应业务需要,现在系统需要有很多输入框仅支持输入正负数,可以控制最大值,超过最大值、最小值出现红框提示、给出错误提示等详细的补充。使用的地方比较多,特此抽取数字输入框供项目组使用。
一、核心功能
- 基础输入控制
- 仅允许输入数字(含小数点/负号)
- 输入时实时格式化(如固定小数位、非负数的过滤、超过最大位数后的截取)
- 最大/最小值范围限制
- 交互优化
- 粘贴内容过滤(自动去除非数字字符)
- 键盘事件拦截(阻止非数字按键输入)
- 鼠标滚轮增减数值
- 空值/非法输入提示
- 兼容性处理
- 支持整数、浮点数、等多种格式
- 多语言/地区数字格式适配(如逗号分隔)
- 与Vue3响应式数据双向绑定(v-model)
二、技术实现思路
封装组件结构
<el-input
v-model.trim="inputValue"
v-bind="$attrs"
:placeholder="placeholder"
@input="validateInput"
@change="changeInput"
:disabled="disabled"
:clearable="clearable"
:class="{ error: isError }"
:title="errorMessage"
></el-input>
核心逻辑
基于 Element Plus 的 el-input 组件进行二次封装
双向绑定:
v-model.trim:自动去除首尾空格(结合原生修饰符)- 通过
$attrs传递未显式声明的属性(如size、prefix-icon等)
事件处理:
@input:实时输入时触发格式校验@change:失去焦点或回车时触发值变更
状态控制:
-
:disabled控制输入框禁用状态 -
:clearable:显示清除按钮
动态样式
:class="{ error: isError }":错误状态时应用红色边框:title="errorMessage":悬浮显示错误提示
三、脚本逻辑解析
1. Props 定义
const props = defineProps({
allowNegative: { type: Boolean, default: false }, // 是否允许负数
disabled: { type: Boolean, default: false }, // 禁用状态
clearable: { type: Boolean, default: false }, // 清除按钮
value: { default: null }, // 输入值
maxDecimalDigits: { type: [String, Number], default: 4 }, // 最大小数位数
maxIntegerDigits: { type: [String, Number], default: 16 }, // 最大整数位数
isPositive: { type: Boolean, default: false }, // 是否为正整数
placeholder: { type: String, default: '请输入' }, // 占位符
maxValue: { type: Number, default: null }, // 最大值限制
maxValueValidTips: { type: String, default: '' } // 超限提示文案
})
设计特点:
- 支持数字格式的全面控制(正负/小数/位数)
- 提供完整的错误提示体系
- 兼容原生 input 的所有属性(通过
$attrs)
2. Emits 定义
const emit = defineEmits(['update:value', 'change'])
update:value:实现 v-model 的双向绑定change:值变更时的回调通知
3. 响应式变量
const inputValue = ref(props.value)
- 使用
ref创建响应式数据 - 初始化值为传入的
props.value
4. 计算属性
const isError = computed(() => {
return props.maxValue !== null && inputValue.value > props.maxValue
})
const errorMessage = computed(() => {
return isError.value ? props.maxValueValidTips : ''
})
- 实时校验数值是否超限
- 动态生成错误提示信息
5. 核心校验逻辑
const validateInput = (value) => {
// 1. 字符过滤
value = value.replace(/[^\d.]/g, '') // 移除非数字和小数点
value = value.replace(/^./g, '') // 阻止首字符为小数点
// 2. 小数点处理
value = value.replace(/.{2,}/g, '.') // 合并多个小数点
const parts = value.split('.') // 分割整数和小数部分
// 3. 位数限制
if (parts[0].length > props.maxIntegerDigits) {
value = parts[0].slice(0, props.maxIntegerDigits)
}
if (parts[1]?.length > props.maxDecimalDigits) {
value = parts[0] + '.' + parts[1].slice(0, props.maxDecimalDigits)
}
// 4. 特殊格式处理
value = value.replace(/^0+\d+?/, '0') // 处理前导零
if (props.isPositive) {
value = value.replace(/^(0+)|[^\d]+/, '') // 强制正整数
}
// 5. 最终赋值
inputValue.value = value
emit('update:value', value)
}
关键处理步骤:
- 字符过滤:通过正则表达式限制输入字符范围
- 小数控制:分割处理整数和小数部分
- 位数限制:动态截断超限数字
- 格式优化:处理前导零和非法字符
- 类型转换:确保最终值为合法数字字符串
四、特性解析
1. 输入格式控制
-
数字范围:通过
maxIntegerDigits和maxDecimalDigits限制位数 -
符号控制:
allowNegative控制正负号 -
特殊类型:
isPositive:强制输入正整数(0 被排除)maxValue:绝对值上限校验
2. 错误处理机制
- 实时校验:输入时立即检测超限
- 视觉反馈:通过 CSS 类
.error改变边框颜色 - 提示文案:悬浮显示自定义错误信息
3. 生命周期管理
watch(
() => props.value,
(val) => {
inputValue.value = val
},
{ deep: true }
)
- 监听
props.value变化,实现父子组件双向同步 - 使用
deep: true确保深层对象更新触发
五、样式实现
.error {
:deep(.el-input__wrapper) {
border: 1px solid #e34d59; // 覆盖 Element Plus 默认样式
}
}
- 作用域样式:
scoped保证样式局部作用域 - 深度选择器:
:deep()修改子组件样式
六、使用场景
该组件适用于:
- 表单中的数值输入场景
- 需要精确控制数字格式的场景(如金额、数量)
- 需要实时校验输入合法性的场景
- 需要兼容 Element Plus 样式的定制化输入组件
改进建议
- 增强类型安全:
interface InputNumberProps {
allowNegative: boolean
maxDecimalDigits: number
// 其他属性...
}
const props = defineProps<InputNumberProps>()
2. 添加输入防抖:
import { debounce } from 'lodash'
const debouncedValidate = debounce(validateInput, 300)
源码示例
<template>
<el-input
v-model.trim="inputValue"
v-bind="$attrs"
:placeholder="placeholder"
@input="validateInput"
@change="changeInput"
:disabled="disabled"
:clearable="clearable"
:class="{ error: isError }"
:title="errorMessage"
></el-input>
</template>
<script setup name="InputNumber">
import debounce from 'lodash/debounce'
import { formInputNumber } from '@/utils/number'
const props = defineProps({
allowNegative: {
// 是否允许负数
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
clearable: {
type: Boolean,
default: false
},
value: {
default: null
},
maxDecimalDigits: {
// 小数后位数
type: [String, Number],
default: 4
},
maxIntegerDigits: {
// 整数位 16 最大值为15亿就是10位,只控制位数,不控制具体值
type: [String, Number],
default: 16
},
isPositive: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: '请输入'
},
// 最大值
maxValue: {
type: Number,
default: null
},
// 超过最大值时的提示文案
maxValueValidTips: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:value', 'change'])
const inputValue = ref(props.value)
// 是否显示错误样式
const isError = computed(() => {
if (props.maxValue !== null && inputValue.value !== null) {
return inputValue.value > props.maxValue
}
return false
})
// 错误提示文案
const errorMessage = computed(() => {
if (isError.value) {
return props.maxValueValidTips
}
return ''
})
const validateInput = debounce((value) => {
// 先把非数字的都替换掉,除了数字和.
if (!props.allowNegative) {
value = value.replace(/[^\d.]/g, '')
} else {
value = value.replace(/(?!^-)[^\d.]/g, '')
// value = handleNegative(value+'')
}
// 保证只有出现一个.而没有多个.
value = value.replace(/.{2,}/g, '.')
// 必须保证第一个为数字而不是.
value = value.replace(/^./g, '')
// 保证.只出现一次,而不能出现两次以上
value = value.replace('.', '$#$').replace(/./g, '').replace('$#$', '.')
// 只能输入4个小数
const parts = value.split('.')
if (parts.length === 1) {
if (parts[0].length > props.maxIntegerDigits) {
value = parts[0].slice(0, props.maxIntegerDigits)
}
} else if (parts.length > 1 && parts[0].length > props.maxIntegerDigits) {
value = parts[0].slice(0, props.maxIntegerDigits) + '.' + parts[1]
}
if (parts.length > 1 && parts[1].length > props.maxDecimalDigits) {
value = parts[0] + '.' + parts[1].slice(0, props.maxDecimalDigits)
}
// 开头只能输入一个0
value = value.replace(/^0+\d+?/g, '0')
// 非负整数 包含0
if (props.maxDecimalDigits === 0) {
// 去除开头可能出现的0(除了单个0的情况)
value = value.replace(/^0{2,}/, '0')
// 去除非数字字符
value = value.replace(/\D/g, '')
}
// 正整数 不包含0,必须>=1
if (props.isPositive) {
value = value.replace(/^(0+)|[^\d]+/g, '')
}
// 整数位和小数位超过位数后自动截取
value = formInputNumber(value, props.maxDecimalDigits, props.maxIntegerDigits)
inputValue.value = value
console.log('inputNumber', value)
emit('update:value', value)
}, 300)
const changeInput = (value) => {
emit('change', value)
}
watch(
() => props.value,
(val) => {
inputValue.value = val
},
{
deep: true
}
)
</script>
<style lang="scss" scoped>
.error {
:deep(.el-input__wrapper) {
border: 1px solid #e34d59;
}
}
</style>
使用示例
<template>
<td class="value-col">
<InputNumber
v-if="canEditPage && row.month !== 'total'"
v-model:value="row.topAmount"
:allowNegative="true"
:maxDecimalDigits="2"
@change="changeEletricAmount(row, 'topAmount')"
:maxIntegerDigits="10"
:maxValue="15000000000"
maxValueValidTips="增量金额不能超过15亿元"
/>
<span v-else>{{ toThousands(row.topAmount) }}</span>
</td>
</template>
以上内容可根据具体需求进一步细化实现细节。 该文章仅用于学习记录,不涉及商业传播。