Vue3高阶封装:实现一个支持格式化与防误触的金额输入框

Vue3高阶封装:实现一个支持格式化与防误触的金额输入框

灵感来源

在日常开发中,金额输入框是高频组件之一,但原生<el-input>存在以下痛点:

  1. 格式化显示:需手动处理千分位分隔符和小数点位数
  2. 防误触输入:需过滤非法字符(如多个小数点、负号位置异常等)
  3. 状态同步:需处理焦点/失焦时的显示与实际值同步

    基于此,本文通过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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值