yunshangxie/tuniao-ui/components/tn-number-box/tn-number-box.vue

402 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>