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

Vue数字输入框光标位置获取及金额输入限制问题排查

金额输入框光标位置判断问题:selectionStart/selectionEnd始终返回0

需求背景

我要给金额输入框做体验优化:当小数位补全(比如1.00)时,允许用户修改整数部分,但禁止在小数位后继续输入。目前输入框在@blur时会自动补全小数位:

  • 输入1 → 1.00
  • 输入1. → 1.00
  • 输入1.5 → 1.50

实现思路

用正则/((\d*\.){1}(\d{2}))/判断输入值是否已补全小数位。如果已补全,就通过光标位置判断:

  • 光标在小数点前(如100|.00):允许输入
  • 光标在小数点后(如100.|00):禁止输入

已知Chrome在2014年移除了type="number"输入框的selectionStart/selectionEnd支持,所以我尝试临时把输入框类型改成text来获取光标位置,但这两个属性始终返回0,无法判断位置。

代码展示

模板部分

<v-container>
    <v-row v-for="(exp, index) in travelExpenseArray" :key="exp.id" style="margin-top: -20px;" >
        <v-col v-show="$vuetify.breakpoint.smAndUp" cols="auto" sm="2" />
        <v-col cols="auto" sm="4" >
            <v-checkbox  v-model="exp.checked" :label="exp.title" dense @change="checkEnabled(index, exp.checked)"/>
        </v-col>
        <v-col cols="auto" sm="4">
            <v-card-text style="padding: 0px;">{{exp.title}} Expenses</v-card-text>
            <v-text-field :ref="`vtf${index}`" outlined step=".01" v-model="exp.expense" type="number" hide-details
            dense single-line :disabled="!exp.checked" hide-spin-buttons @keypress="checkValidKey($event, index)" 
            @click="clearCurrencyField(index)" @blur="appendZeros(index)"/>
        </v-col>
        <v-col v-show="$vuetify.breakpoint.smAndUp" cols="auto" sm="2" />
    </v-row>

    <v-row style="margin-top: -20px;">
        <v-col cols="auto" sm="6" />
        <v-col cols="auto" sm="4">
            <v-card-text style="padding: 0px">Total Travel Expenses</v-card-text>
            <v-text-field outlined v-model="totalTravelExpenses" dense disabled />
        </v-col>
        <v-col v-show="$vuetify.breakpoint.smAndUp" cols="auto" sm="2" />
    </v-row>
</v-container>

脚本部分(checkValidKey方法)

checkValidKey(evt, index){
    let expense = this.travelExpenseArray[index].expense;               
    // 获取按键编码
    const charCode = evt.which ? evt.which : evt.keyCode;

    // 校验按键是否合法(仅允许数字和小数点)
    if(!this.getValidKeys().includes(charCode)){
        evt.preventDefault();

    // 如果输入值已补全小数位,判断光标位置
    }else if((/((\d*\.){1}(\d{2}))/).test(expense)){

        // 临时切换为text类型以获取光标位置
        this.$refs[`vtf${index}`][0].$refs.input.type = "text";
        console.log(this.$refs[`vtf${index}`][0].$refs.input.selectionStart);
        let selection = {
            start: this.$refs[`vtf${index}`][0].$refs.input.selectionStart,
            end: this.$refs[`vtf${index}`][0].$refs.input.selectionEnd
        }
                
        // 判断光标是否在小数位区域,若是则阻止输入
        if(selection.start >= selection.end - 2){
            evt.preventDefault();
        }
        // 切换回number类型
        this.$refs[`vtf${index}`][0].$refs.input.type = "number";
                
        return;
    }
},

Vuex中travelExpenseArray结构

travelExpenseArray: [
    {id: 0, title: 'Bus', checked: false, expense: '0.00'},
    {id: 1, title: 'Car', checked: false, expense: '0.00'},
    {id: 2, title: 'Plane', checked: false, expense: '0.00'},
    {id: 3, title: 'Train', checked: false, expense: '0.00'},
],

问题点

临时切换输入框类型为text后,selectionStart和selectionEnd始终返回0,无法正确判断光标位置,导致无法实现基于光标位置的输入限制。


解决方案

1. 避免动态切换输入框类型

直接使用type="text"输入框,手动处理数字输入校验和格式补全,这样可以稳定获取selectionStart/selectionEnd。修改模板中的v-text-field:

<v-text-field :ref="`vtf${index}`" outlined v-model="exp.expense" type="text" hide-details
dense single-line :disabled="!exp.checked" @keypress="checkValidKey($event, index)" 
@click="clearCurrencyField(index)" @blur="appendZeros(index)"/>

2. 修正光标位置判断逻辑

在checkValidKey中,无需切换类型,直接获取光标位置,然后判断是否在小数点后两位区域:

checkValidKey(evt, index){
    const inputEl = this.$refs[`vtf${index}`][0].$refs.input;
    const expense = this.travelExpenseArray[index].expense;               
    const charCode = evt.which ? evt.which : evt.keyCode;

    // 校验合法按键:数字、小数点,以及退格/删除键
    const validKeys = [8, 46, 48,49,50,51,52,53,54,55,56,57, 96,97,98,99,100,101,102,103,104,105, 110,190];
    if(!validKeys.includes(charCode)){
        evt.preventDefault();
        return;
    }

    // 如果是退格/删除键,直接允许
    if([8,46].includes(charCode)) return;

    // 判断是否已补全小数位
    if((/^\d+\.\d{2}$/).test(expense)){
        const cursorPos = inputEl.selectionStart;
        const dotIndex = expense.indexOf('.');
        // 如果光标在小数点后(包括小数点位置),阻止输入
        if(cursorPos > dotIndex){
            evt.preventDefault();
        }
    }
},

3. 优化格式补全逻辑

确保appendZeros方法正确处理输入值:

appendZeros(index){
    let val = this.travelExpenseArray[index].expense;
    if(!val) val = '0.00';
    // 处理无小数点的情况
    if(!val.includes('.')){
        val = `${val}.00`;
    }else{
        // 处理小数点后位数不足的情况
        const parts = val.split('.');
        if(parts[1].length < 2){
            parts[1] = parts[1].padEnd(2, '0');
            val = parts.join('.');
        }
    }
    this.travelExpenseArray[index].expense = val;
},

4. 点击输入框时的处理(可选)

如果需要点击时清空默认的0.00,可以修改clearCurrencyField:

clearCurrencyField(index){
    const inputEl = this.$refs[`vtf${index}`][0].$refs.input;
    if(this.travelExpenseArray[index].expense === '0.00'){
        this.travelExpenseArray[index].expense = '';
        // 聚焦后设置光标到起始位置
        setTimeout(() => {
            inputEl.selectionStart = inputEl.selectionEnd = 0;
        }, 0);
    }
},

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 18:51:31