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
相关产品推荐
相关产品推荐

