Vue3高阶封装:实现一个支持格式化与防误触的金额输入框
灵感来源
在日常开发中,金额输入框是高频组件之一,但原生<el-input>
存在以下痛点:
- 格式化显示:需手动处理千分位分隔符和小数点位数
- 防误触输入:需过滤非法字符(如多个小数点、负号位置异常等)
- 状态同步:需处理焦点/失焦时的显示与实际值同步
基于此,本文通过Vue3组合式API实现一个高复用性的金额输入框组件。
核心功能实现
vue
<template>
<el-input
ref="inputRef"
v-model="displayValue"
:placeholder="placeholder"
:disabled="disabled"
:size="size"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
>
<!-- 插槽扩展 -->
<template v-if="$slots.prepend" #prepend>
<slot name="prepend"></slot>
</template>
<!-- 其他插槽类似 -->
</el-input>
</template>
<script setup>
import { ref, watch, computed } from 'vue'
const props = defineProps({
modelValue: [Number, String],
precision: { type: Number, default: 2 },
disabled: Boolean,
size: String,
placeholder: { type: String, default: '请输入金额' }
})
const emit = defineEmits(['update:modelValue'])
const inputRef = ref(null)
const isFocus = ref(false)
// 双向绑定代理
const displayValue = ref('')
const actualValue = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
// 格式化显示(千分位+小数点)
const formatNumber = (num) => {
if (!num) return ''
const parts = Number(num).toFixed(props.precision).split('.')
return `${parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')}.${parts[1] || ''}`
}
// 解析输入值
const parseNumber = (str) => parseFloat(str.replace(/,/g, '')) || 0
// 输入过滤逻辑
const handleInput = (value) => {
if (!isFocus.value) return
// 1. 基础过滤(允许负号开头,禁止其他非法字符)
let filtered = value.replace(/[^-0-9.]/g, '')
// 2. 多小数点处理
const dotCount = (filtered.match(/\./g) || []).length
if (dotCount > 1) filtered = filtered.replace(/\.+$/, '')
// 3. 负号位置校验
if (filtered.indexOf('-') > 0) filtered = filtered.replace(/-/g, '')
displayValue.value = filtered
actualValue.value = parseNumber(filtered)
}
// 焦点事件处理
const handleFocus = () => {
isFocus.value = true
displayValue.value = actualValue.value.toString()
}
const handleBlur = () => {
isFocus.value = false
displayValue.value = formatNumber(actualValue.value)
}
// 响应外部值变化
watch(() => props.modelValue, (val) => {
if (!isFocus.value) {
displayValue.value = formatNumber(val)
}
}, { immediate: true })
</script>
关键设计解析
1. 插槽扩展机制
通过v-if
动态渲染prepend/append/prefix/suffix
插槽,完美兼容Element Plus原生插槽机制
vue
<template v-if="$slots.prepend" #prepend>
<slot name="prepend"></slot>
</template>
2. 双向绑定优化
采用computed
代理实现响应式更新,通过watch
监听外部值变化实现双向同步
javascript
const actualValue = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
3. 输入防误触策略
- 非法字符过滤:正则
/[^-0-9.]/g
禁止非数字、非小数点、非负号的输入 - 多小数点处理:通过
match
统计小数点数量,保留最后一个有效小数点 - 负号位置校验:确保负号只能出现在开头位置
4. 状态管理
通过isFocus
标志位区分输入态与展示态,实现:
- 聚焦时显示原始数值
- 失焦时格式化显示
- 外部值变化时自动格式化
使用示例
vue
<template>
<MoneyInput v-model="amount" :precision="2" placeholder="请输入金额" />
</template>
<script setup>
import MoneyInput from './MoneyInput.vue'
const amount = ref(1234567.89)
</script>