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

402 lines
11 KiB
Vue
Raw Normal View History

2023-12-25 17:56:30 +08:00
<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>