402 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
		
		
			
		
	
	
			402 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
|  | <template> | |||
|  |   <view class="tn-number-box-class tn-number-box"> | |||
|  |     <!-- 减 --> | |||
|  |     <view | |||
|  |       class="tn-number-box__btn__minus" | |||
|  |       :class="[ | |||
|  |         backgroundColorClass, | |||
|  |         fontColorClass, | |||
|  |         {'tn-number-box__btn--disabled': disabled || inputValue <= min} | |||
|  |       ]" | |||
|  |       :style="{ | |||
|  |         backgroundColor: backgroundColorStyle, | |||
|  |         height: $tn.string.getLengthUnitValue(inputHeight), | |||
|  |         color: fontColorStyle, | |||
|  |         fontSize: fontSizeStyle | |||
|  |       }" | |||
|  |       @touchstart.stop.prevent="touchStart('minus')" | |||
|  |       @touchend.stop.prevent="clearTimer" | |||
|  |     > | |||
|  |       <view class="tn-icon-reduce"></view> | |||
|  |     </view> | |||
|  |      | |||
|  |     <!-- 输入框 --> | |||
|  |     <input | |||
|  |       v-model="inputValue" | |||
|  |       :disabled="disabledInput || disabled" | |||
|  |       :cursor-spacing="getCursorSpacing" | |||
|  |       class="tn-number-box__input" | |||
|  |       :class="[ | |||
|  |         fontColorClass, | |||
|  |         {'tn-number-box__input--disabled': disabledInput || disabled} | |||
|  |       ]" | |||
|  |       :style="{ | |||
|  |         width: $tn.string.getLengthUnitValue(inputWidth), | |||
|  |         height: $tn.string.getLengthUnitValue(inputHeight), | |||
|  |         color: fontColorStyle, | |||
|  |         fontSize: fontSizeStyle, | |||
|  |         backgroundColor: backgroundColorStyle | |||
|  |       }" | |||
|  |       @blur="blurInput" | |||
|  |       @focus="focusInput" | |||
|  |     /> | |||
|  |      | |||
|  |     <!-- 加 --> | |||
|  |     <view | |||
|  |       class="tn-number-box__btn__plus" | |||
|  |       :class="[ | |||
|  |         backgroundColorClass, | |||
|  |         fontColorClass, | |||
|  |         {'tn-number-box__btn--disabled': disabled || inputValue >= max} | |||
|  |       ]" | |||
|  |       :style="{ | |||
|  |         backgroundColor: backgroundColorStyle, | |||
|  |         height: $tn.string.getLengthUnitValue(inputHeight), | |||
|  |         color: fontColorStyle, | |||
|  |         fontSize: fontSizeStyle | |||
|  |       }" | |||
|  |       @touchstart.stop.prevent="touchStart('plus')" | |||
|  |       @touchend.stop.prevent="clearTimer" | |||
|  |     > | |||
|  |       <view class="tn-icon-add"></view> | |||
|  |     </view> | |||
|  |   </view> | |||
|  | </template> | |||
|  | 
 | |||
|  | <script> | |||
|  |   import componentsColor from '../../libs/mixin/components_color.js' | |||
|  |    | |||
|  |   export default { | |||
|  |     mixins: [componentsColor], | |||
|  |     name: 'tn-number-box', | |||
|  |     props: { | |||
|  |       value: { | |||
|  |         type: Number, | |||
|  |         default: 1 | |||
|  |       }, | |||
|  |       // 索引
 | |||
|  |       index: { | |||
|  |         type: [Number, String], | |||
|  |         default: '' | |||
|  |       }, | |||
|  |       // 最小值
 | |||
|  |       min: { | |||
|  |         type: Number, | |||
|  |         default: 0 | |||
|  |       }, | |||
|  |       // 最大值
 | |||
|  |       max: { | |||
|  |         type: Number, | |||
|  |         default: 99999 | |||
|  |       }, | |||
|  |       // 步进值
 | |||
|  |       step: { | |||
|  |         type: Number, | |||
|  |         default: 1 | |||
|  |       }, | |||
|  |       // 禁用
 | |||
|  |       disabled: { | |||
|  |         type: Boolean, | |||
|  |         default: false | |||
|  |       }, | |||
|  |       // 是否禁用输入
 | |||
|  |       disabledInput: { | |||
|  |         type: Boolean, | |||
|  |         default: false | |||
|  |       }, | |||
|  |       // 输入框的宽度
 | |||
|  |       inputWidth: { | |||
|  |         type: Number, | |||
|  |         default: 88 | |||
|  |       }, | |||
|  |       // 输入框的高度
 | |||
|  |       inputHeight: { | |||
|  |         type: Number, | |||
|  |         default: 50 | |||
|  |       }, | |||
|  |       // 输入框和键盘之间的距离
 | |||
|  |       cursorSpacing: { | |||
|  |         type: Number, | |||
|  |         default: 100 | |||
|  |       }, | |||
|  |       // 是否开启长按进行连续递增减
 | |||
|  |       longPress: { | |||
|  |         type: Boolean, | |||
|  |         default: true | |||
|  |       }, | |||
|  |       // 长按触发间隔
 | |||
|  |       longPressTime: { | |||
|  |         type: Number, | |||
|  |         default: 250 | |||
|  |       }, | |||
|  |       // 是否只能输入正整数
 | |||
|  |       positiveInteger: { | |||
|  |         type: Boolean, | |||
|  |         default: true | |||
|  |       } | |||
|  |     }, | |||
|  |     computed: { | |||
|  |       getCursorSpacing() { | |||
|  |         return Number(uni.upx2px(this.cursorSpacing)) | |||
|  |       } | |||
|  |     }, | |||
|  |     data() { | |||
|  |       return { | |||
|  |         // 输入框的值
 | |||
|  |         inputValue: 1, | |||
|  |         // 长按定时器
 | |||
|  |         longPressTimer: null, | |||
|  |         // 标记值的改变是来自外部还是内部
 | |||
|  |         changeFromInner: false, | |||
|  |         // 内部定时器
 | |||
|  |         innerChangeTimer: null | |||
|  |       } | |||
|  |     }, | |||
|  |     watch: { | |||
|  |       value(val) { | |||
|  |         // 只有value的改变是来自外部的时候,才去同步inputValue的值,否则会造成循环错误
 | |||
|  |         if (!this.changeFromInner) { | |||
|  |           this.updateInputValue() | |||
|  |           // 因为inputValue变化后,会触发this.handleChange(),在其中changeFromInner会再次被设置为true,
 | |||
|  |           // 造成外面修改值,也导致被认为是内部修改的混乱,这里进行this.$nextTick延时,保证在运行周期的最后处
 | |||
|  |           // 将changeFromInner设置为false
 | |||
|  |           this.$nextTick(() => { | |||
|  |           	this.changeFromInner = false | |||
|  |           }) | |||
|  |         } | |||
|  |       }, | |||
|  |       inputValue(newVal, oldVal) { | |||
|  |         // 为了让用户能够删除所有输入值,重新输入内容,删除所有值后,内容为空字符串
 | |||
|  |         if (newVal === '') return | |||
|  |         let value = 0 | |||
|  |         // 首先判断是否数值,并且在min和max之间,如果不是,使用原来值
 | |||
|  |         let isNumber = this.$tn.test.number(newVal) | |||
|  |         if (isNumber && newVal >= this.min && newVal <= this.max) value = newVal | |||
|  |         else value = oldVal | |||
|  |          | |||
|  |         // 判断是否只能输入大于等于0的整数
 | |||
|  |         if (this.positiveInteger) { | |||
|  |           // 小于0或者带有小数点
 | |||
|  |           if (newVal < 0 || String(newVal).indexOf('.') !== -1) { | |||
|  |             value = Math.floor(newVal) | |||
|  |             // 双向绑定input的值,必须要使用$nextTick修改显示的值
 | |||
|  |             this.$nextTick(() => { | |||
|  |             	this.inputValue = value | |||
|  |             }) | |||
|  |           } | |||
|  |         } | |||
|  |         this.handleChange(value, 'change') | |||
|  |       }, | |||
|  |       min() { | |||
|  |         this.updateInputValue() | |||
|  |       }, | |||
|  |       max() { | |||
|  |         this.updateInputValue() | |||
|  |       } | |||
|  |     }, | |||
|  |     created() { | |||
|  |       this.updateInputValue() | |||
|  |     }, | |||
|  |     methods: { | |||
|  |       // 开始点击按钮
 | |||
|  |       touchStart(func) { | |||
|  |         // 先执行一遍方法,否则会造成松开手时,就执行了clearTimer,导致无法实现功能
 | |||
|  |         this[func]() | |||
|  |         // 如果没有开启长按功能,直接返回
 | |||
|  |         if (!this.longPress) return | |||
|  |         // 清空长按定时器,防止重复注册
 | |||
|  |         if (this.longPressTimer) { | |||
|  |           clearInterval(this.longPressTimer) | |||
|  |           this.longPressTimer = null | |||
|  |         } | |||
|  |         this.longPressTimer = setInterval(() => { | |||
|  |           // 执行加减操作
 | |||
|  |           this[func]() | |||
|  |         }, this.longPressTime) | |||
|  |       }, | |||
|  |       // 清除定时器
 | |||
|  |       clearTimer() { | |||
|  |         this.$nextTick(() => { | |||
|  |           if (this.longPressTimer) { | |||
|  |             clearInterval(this.longPressTimer) | |||
|  |             this.longPressTimer = null | |||
|  |           } | |||
|  |         }) | |||
|  |       }, | |||
|  |       // 减
 | |||
|  |       minus() { | |||
|  |         this.computeValue('minus') | |||
|  |       }, | |||
|  |       // 加
 | |||
|  |       plus() { | |||
|  |         this.computeValue('plus') | |||
|  |       }, | |||
|  |       // 处理小数相加减出现溢出问题
 | |||
|  |       calcPlus(num1, num2) { | |||
|  |         let baseNum = 0, baseNum1 = 0, baseNum2 = 0 | |||
|  |         try { | |||
|  |           baseNum1 = num1.toString().split('.')[1].length | |||
|  |         } catch(e) { | |||
|  |           baseNum1 = 0 | |||
|  |         } | |||
|  |         try { | |||
|  |           baseNum2 = num2.toString().split('.')[1].length | |||
|  |         } catch(e) { | |||
|  |           baseNum2 = 0 | |||
|  |         } | |||
|  |          | |||
|  |         baseNum = Math.pow(10, Math.max(baseNum1, baseNum2)) | |||
|  |         // 精度
 | |||
|  |         let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2 | |||
|  |         return ((num1 * baseNum + num2 * baseNum) / baseNum).toFixed(precision) | |||
|  |       }, | |||
|  |       calcMinus(num1, num2) { | |||
|  |         let baseNum = 0, baseNum1 = 0, baseNum2 = 0 | |||
|  |         try { | |||
|  |           baseNum1 = num1.toString().split('.')[1].length | |||
|  |         } catch(e) { | |||
|  |           baseNum1 = 0 | |||
|  |         } | |||
|  |         try { | |||
|  |           baseNum2 = num2.toString().split('.')[1].length | |||
|  |         } catch(e) { | |||
|  |           baseNum2 = 0 | |||
|  |         } | |||
|  |          | |||
|  |         baseNum = Math.pow(10, Math.max(baseNum1, baseNum2)) | |||
|  |         // 精度
 | |||
|  |         let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2 | |||
|  |         return ((num1 * baseNum - num2 * baseNum) / baseNum).toFixed(precision) | |||
|  |       }, | |||
|  |       // 处理操作后的值
 | |||
|  |       computeValue(type) { | |||
|  |         uni.hideKeyboard() | |||
|  |         if (this.disabled) return | |||
|  |         let value = 0 | |||
|  |          | |||
|  |         if (type === 'minus') { | |||
|  |           // 减
 | |||
|  |           value = this.calcMinus(this.inputValue, this.step) | |||
|  |         } else if (type === 'plus') { | |||
|  |           // 加
 | |||
|  |           value = this.calcPlus(this.inputValue, this.step) | |||
|  |         } | |||
|  |         // 判断是否比最小值小和操作最大值
 | |||
|  |         if (value < this.min || value > this.max) return | |||
|  |          | |||
|  |         this.inputValue = value | |||
|  |         this.handleChange(value, type) | |||
|  |       }, | |||
|  |       // 处理用户手动输入
 | |||
|  |       blurInput(event) { | |||
|  |         let val = 0, | |||
|  |             value = event.detail.value | |||
|  |         // 如果为非0-9数字组成,或者其第一位数值为0,直接让其等于min值
 | |||
|  |         // 这里不直接判断是否正整数,是因为用户传递的props min值可能为0
 | |||
|  |         if (!/(^\d+$)/.test(value) || value[0] == 0) { | |||
|  |           val = this.min | |||
|  |         } else { | |||
|  |           val = +value | |||
|  |         } | |||
|  |          | |||
|  |         if (val > this.max) { | |||
|  |           val = this.max | |||
|  |         } else if (val < this.min) { | |||
|  |           val = this.min | |||
|  |         } | |||
|  |         this.$nextTick(() => { | |||
|  |           this.inputValue = val | |||
|  |         }) | |||
|  |         this.handleChange(val, 'blur') | |||
|  |       }, | |||
|  |       // 获取焦点
 | |||
|  |       focusInput() { | |||
|  |         this.$emit('focus') | |||
|  |       }, | |||
|  |       // 初始化inputValue
 | |||
|  |       updateInputValue() { | |||
|  |         let value = this.value | |||
|  |         if (value <= this.min) { | |||
|  |           value = this.min | |||
|  |         } else if (value >= this.max) { | |||
|  |           value = this.max | |||
|  |         } | |||
|  |          | |||
|  |         this.inputValue = Number(value) | |||
|  |       }, | |||
|  |       // 处理值改变状态
 | |||
|  |       handleChange(value, type) { | |||
|  |         if (this.disabled) return | |||
|  |         // 清除定时器,防止混乱
 | |||
|  |         if (this.innerChangeTimer) { | |||
|  |           clearTimeout(this.innerChangeTimer) | |||
|  |           this.innerChangeTimer = null | |||
|  |         } | |||
|  |          | |||
|  |         // 内部修改值
 | |||
|  |         this.changeFromInner = true | |||
|  |         // 一定时间内,清除changeFromInner标记,否则内部值改变后
 | |||
|  |         // 外部通过程序修改value值,将会无效
 | |||
|  |         this.innerChangeTimer = setTimeout(() => { | |||
|  |           this.changeFromInner = false | |||
|  |         }, 150) | |||
|  |         this.$emit('input', Number(value)) | |||
|  |         this.$emit(type, { | |||
|  |           value: Number(value), | |||
|  |           index: this.index | |||
|  |         }) | |||
|  |       } | |||
|  |     } | |||
|  |   } | |||
|  | </script> | |||
|  | 
 | |||
|  | <style lang="scss" scoped> | |||
|  |    | |||
|  |   .tn-number-box { | |||
|  |     display: inline-flex; | |||
|  |     align-items: center; | |||
|  |      | |||
|  |     &__btn { | |||
|  |       &__plus,&__minus { | |||
|  |         width: 60rpx; | |||
|  |         display: flex; | |||
|  |         flex-direction: row; | |||
|  |         justify-content: center; | |||
|  |         align-items: center; | |||
|  |         background-color: $tn-font-holder-color; | |||
|  |       } | |||
|  |        | |||
|  |       &__plus { | |||
|  |         border-radius: 0 8rpx 8rpx 0; | |||
|  |       } | |||
|  |        | |||
|  |       &__minus { | |||
|  |         border-radius: 8rpx 0 0 8rpx; | |||
|  |       } | |||
|  |        | |||
|  |       &--disabled { | |||
|  |         color: $tn-font-sub-color !important; | |||
|  |         background: $tn-font-holder-color !important; | |||
|  |       } | |||
|  |     } | |||
|  |      | |||
|  |     &__input { | |||
|  |       display: flex; | |||
|  |       flex-direction: row; | |||
|  |       align-items: center; | |||
|  |       justify-content: center; | |||
|  |       position: relative; | |||
|  |       text-align: center; | |||
|  |       box-sizing: border-box; | |||
|  |       padding: 0 4rpx; | |||
|  |       margin: 0 6rpx; | |||
|  |       background-color: $tn-font-holder-color; | |||
|  |        | |||
|  |       &--disabled { | |||
|  |         color: $tn-font-sub-color !important; | |||
|  |         background: $tn-font-holder-color !important; | |||
|  |       } | |||
|  |     } | |||
|  |   } | |||
|  | </style> |