You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3中使用Cleave.js无需v-model.lazy实现值同步的方案

解决Cleave.js格式化后v-model值不同步问题(无需v-model.lazy)

问题描述

使用Cleave.js为输入数字添加千分位分隔符时,输入1000.123会在输入框显示正确格式1,000.12,但v-model绑定的值仍是1000.123而非格式化后的1000.12。不想使用v-model.lazy(该修饰符仅在输入框失焦时更新值),希望实现实时同步格式化后的值。

当前组件代码

<script setup lang="ts">
import { ref, watch } from 'vue'
const props = withDefaults(defineProps<Props>(), {
  type: 'text',
  required: false,
  readonly: false,
  disabled: false
})
const inputValue = ref(props.modelValue)
const emit = defineEmits<{
  (e: 'update:modelValue', value: string | number): void
}>()

watch(
  () => props.modelValue,
  () => {
    inputValue.value = numeric.format(props.modelValue)
  },
  {
    immediate: true
  }
)

watch(
  inputValue,
  () => {
    emit('update:modelValue', parseFloat(inputValue.value.toString().replace(/(\d+),(?=\d+(\D|$))/g, '$1')))
  },
  {
    immediate: true
  }
)
</script>

<template>
 <input
        class="form-input"
        v-model.lazy="inputValue"
        v-cleave="{ numeral: true, numeralThousandsGroupStyle: 'thousand' }"
        :placeholder="props.placeholder"
        :required="props.required"
        :readonly="props.readonly"
        :disabled="props.disabled"
      />
</template>

解决方案

核心思路是直接利用Cleave实例的内置事件和API同步值,避免手动字符串处理的误差,同时实现实时更新:

方案1:手动初始化Cleave实例(推荐)

通过手动创建Cleave实例,利用其onValueChanged事件实时触发值更新,同时用setRawValue同步父组件传入的初始值。

修改后的代码:

<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
import Cleave from 'cleave.js'

const props = withDefaults(defineProps<{
  modelValue: string | number
  type?: string
  required?: boolean
  readonly?: boolean
  disabled?: boolean
  placeholder?: string
}>(), {
  type: 'text',
  required: false,
  readonly: false,
  disabled: false
})

const inputRef = ref<HTMLInputElement | null>(null)
const inputValue = ref(props.modelValue)
const emit = defineEmits<{
  (e: 'update:modelValue', value: string | number): void
}>()

// 同步父组件传入的modelValue到输入框
watch(
  () => props.modelValue,
  (newVal) => {
    if (inputRef.value?._cleave) {
      inputRef.value._cleave.setRawValue(newVal)
    } else {
      inputValue.value = newVal
    }
  },
  { immediate: true }
)

onMounted(() => {
  if (!inputRef.value) return
  
  // 手动初始化Cleave,配置小数位数保证和显示一致
  new Cleave(inputRef.value, {
    numeral: true,
    numeralThousandsGroupStyle: 'thousand',
    numeralDecimalScale: 2, // 固定保留2位小数,匹配显示效果
    onValueChanged: (event) => {
      // 移除千分位分隔符,转成数字格式
      const processedValue = Number(event.target.value.replace(/,/g, ''))
      emit('update:modelValue', processedValue)
      // 若需要字符串格式,替换为:emit('update:modelValue', event.target.value.replace(/,/g, ''))
    }
  })
})
</script>

<template>
  <input
    ref="inputRef"
    class="form-input"
    :value="inputValue"
    :placeholder="props.placeholder"
    :required="props.required"
    :readonly="props.readonly"
    :disabled="props.disabled"
  />
</template>

方案2:复用v-cleave指令

如果想保留v-cleave指令,可在组件挂载后获取指令创建的Cleave实例,添加事件监听:

<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'

const props = withDefaults(defineProps<{
  modelValue: string | number
  type?: string
  required?: boolean
  readonly?: boolean
  disabled?: boolean
  placeholder?: string
}>(), {
  type: 'text',
  required: false,
  readonly: false,
  disabled: false
})

const inputRef = ref<HTMLInputElement | null>(null)
const inputValue = ref(props.modelValue)
const emit = defineEmits<{
  (e: 'update:modelValue', value: string | number): void
}>()

watch(
  () => props.modelValue,
  (newVal) => {
    if (inputRef.value?._cleave) {
      inputRef.value._cleave.setRawValue(newVal)
    } else {
      inputValue.value = newVal
    }
  },
  { immediate: true }
)

onMounted(() => {
  const cleaveInstance = inputRef.value?._cleave
  if (!cleaveInstance) return

  // 覆盖Cleave的onValueChanged事件,实时同步值
  cleaveInstance.options.onValueChanged = (event) => {
    const processedValue = Number(event.target.value.replace(/,/g, ''))
    emit('update:modelValue', processedValue)
  }
})
</script>

<template>
  <input
    ref="inputRef"
    class="form-input"
    :value="inputValue"
    v-cleave="{ numeral: true, numeralThousandsGroupStyle: 'thousand', numeralDecimalScale: 2 }"
    :placeholder="props.placeholder"
    :required="props.required"
    :readonly="props.readonly"
    :disabled="props.disabled"
  />
</template>

关键说明

  • numeralDecimalScale: 2:明确设置小数位数,确保输入1000.123时自动截断为2位,和显示的1,000.12完全匹配
  • onValueChanged事件:Cleave内置的实时输入监听事件,每次输入内容变化都会触发,无需依赖v-model.lazy
  • setRawValue:用于同步父组件传入的值,保证输入框显示正确的格式化内容

内容的提问来源于stack exchange,提问作者Kangkung Gamers

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 12:57:33