201 lines
3.8 KiB
Vue
201 lines
3.8 KiB
Vue
<template>
|
||
<div class="custom-number-box">
|
||
<div
|
||
class="number-btn number-btn-minus"
|
||
:class="{ disabled: isMinDisabled || isDisabled }"
|
||
@click="handleMinus"
|
||
>
|
||
<span>-</span>
|
||
</div>
|
||
|
||
<input
|
||
v-model="inputValue"
|
||
class="number-input"
|
||
type="text"
|
||
:disabled="isDisabled"
|
||
@input="handleInput"
|
||
@blur="handleBlur"
|
||
/>
|
||
|
||
<div
|
||
class="number-btn number-btn-plus"
|
||
:class="{ disabled: isMaxDisabled || isDisabled }"
|
||
@click="handlePlus"
|
||
>
|
||
<span>+</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, watch, computed } from 'vue'
|
||
|
||
const props = defineProps({
|
||
modelValue: {
|
||
type: [Number, String],
|
||
default: ''
|
||
},
|
||
min: {
|
||
type: Number,
|
||
default: 1
|
||
},
|
||
max: {
|
||
type: Number,
|
||
default: 28
|
||
},
|
||
step: {
|
||
type: Number,
|
||
default: 1
|
||
},
|
||
disabled: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
// ✅ 新增:是否整数(默认 true)
|
||
integer: {
|
||
type: Boolean,
|
||
default: true
|
||
}
|
||
})
|
||
|
||
const emit = defineEmits(['update:modelValue'])
|
||
|
||
const inputValue = ref('')
|
||
const isDisabled = computed(() => props.disabled)
|
||
|
||
// 监听外部值
|
||
watch(
|
||
() => props.modelValue,
|
||
(val) => {
|
||
if (val === '' || val === null || val === undefined) {
|
||
inputValue.value = ''
|
||
return
|
||
}
|
||
const num = Number(val)
|
||
inputValue.value = isNaN(num) ? '' : String(num)
|
||
},
|
||
{ immediate: true }
|
||
)
|
||
|
||
// 减号禁用
|
||
const isMinDisabled = computed(() => {
|
||
if (inputValue.value === '') return true
|
||
const val = Number(inputValue.value)
|
||
return val <= props.min
|
||
})
|
||
|
||
// 加号禁用
|
||
const isMaxDisabled = computed(() => {
|
||
if (inputValue.value === '') return false
|
||
const val = Number(inputValue.value)
|
||
return val >= props.max
|
||
})
|
||
|
||
// 减
|
||
function handleMinus() {
|
||
if (inputValue.value === '') return
|
||
let val = Number(inputValue.value) - props.step
|
||
val = Math.max(val, props.min)
|
||
if (props.integer) val = Math.round(val)
|
||
inputValue.value = String(val)
|
||
emit('update:modelValue', val)
|
||
}
|
||
|
||
// 加
|
||
function handlePlus() {
|
||
let val
|
||
if (inputValue.value === '') {
|
||
val = props.min
|
||
} else {
|
||
val = Number(inputValue.value) + props.step
|
||
}
|
||
val = Math.min(val, props.max)
|
||
if (props.integer) val = Math.round(val)
|
||
inputValue.value = String(val)
|
||
emit('update:modelValue', val)
|
||
}
|
||
|
||
// 输入时处理
|
||
function handleInput() {
|
||
let val = inputValue.value.trim()
|
||
if (val === '') {
|
||
emit('update:modelValue', '')
|
||
return
|
||
}
|
||
|
||
// 如果是整数,禁止输入小数点
|
||
if (props.integer) val = val.replace(/\./g, '')
|
||
|
||
const num = Number(val)
|
||
if (isNaN(num)) {
|
||
inputValue.value = ''
|
||
emit('update:modelValue', '')
|
||
return
|
||
}
|
||
|
||
inputValue.value = val
|
||
emit('update:modelValue', num)
|
||
}
|
||
|
||
// 失焦校验
|
||
function handleBlur() {
|
||
const val = inputValue.value.trim()
|
||
if (val === '') {
|
||
emit('update:modelValue', '')
|
||
return
|
||
}
|
||
|
||
let num = Number(val)
|
||
if (isNaN(num)) {
|
||
inputValue.value = ''
|
||
emit('update:modelValue', '')
|
||
return
|
||
}
|
||
|
||
// 限制范围
|
||
num = Math.max(props.min, Math.min(props.max, num))
|
||
|
||
// ✅ 整数控制
|
||
if (props.integer) num = Math.round(num)
|
||
|
||
inputValue.value = String(num)
|
||
emit('update:modelValue', num)
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.custom-number-box {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
border: 1px solid #e4e7ed;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
user-select: none;
|
||
}
|
||
|
||
.number-btn {
|
||
width: 36px;
|
||
height: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: #f5f7fa;
|
||
cursor: pointer;
|
||
font-size: 24px;
|
||
}
|
||
|
||
.number-btn.disabled {
|
||
color: #c0c4cc;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.number-input {
|
||
width: 50px;
|
||
height: 32px;
|
||
text-align: center;
|
||
border: none;
|
||
outline: none;
|
||
font-size: 14px;
|
||
padding: 0;
|
||
}
|
||
</style> |