目标
做一个金额输入框
要求
只需输入 数字 或者 小数点
不准输入非法数字 如 1.1. 或者 1..2
数字最大输入到两位小数 如 1.22, 不可输入 1.222
此时我们只讨论系统键盘。 自定义键盘不可取因为输入框不能获取焦点, 自定义键盘需要搭配特定的输入框也就是没有光标的那种才合适
解决方案的常规手段无外乎一下三种
-
监听 keyup/keydown/keypress 事件 通过 event.keycode 判断keycode的值来 return false还是true 此种体验最优,页面没有闪动 ( 存在部分兼容性)
-
监听 keyup/keydown/keypress 事件 通过 event.target.value 得到当前输入的值 通过 正则匹配来过滤是否替换为空 ( 存在部分兼容性)
-
监听 input 事件 通过 event.target.value 得到当前全部的输入值,但是当用户移动光标后在输入无法得知当前输入的是什么值, ( 完全兼容,但是没有什么乱用)
所以解决方案只有通过第一条或者第二条
在分析一下 input type 的取值
- type =text 弹出的非数字键盘 体验最差 有限排除
- type = number 弹出数字键盘,缺点 event.target.value 只能取到被过滤后的数字
- type= tel 弹出数字键盘 event.target.value 可以取到正确值, 但是在ios下弹出的软键盘 没有'.'
先来一段小前提,一下为亲测
-
在中文状态下在keycode 都是229
-
Android手机 弹出的软键盘默认为中文状态 所以code码 都是229
-
ios手机 弹出的软键盘默认为英文文状态 所以code码可以用
-
android手机无法监听到keypress事件,只可以监听到keyup keydown
-
ios手机在
<input type=tel>
弹出的软键盘 只有1-9 没有 ‘.’ -
<input type="number">
这种形式 event.target.value 得到的值只有纯数字,也就是输入12.... 的时候 event.target.value =12
所以解决方案呼之欲出
- 在android 下
input type=tel
监听 keyup事件, event.target.value 通过正则过滤输入 - 在ios下
input type=number
通过 keypress 事件 event.keycode 过滤输入,
部分参考 https://cloud.tencent.com/developer/article/1098877
一下是我对以上的一个简单实现
<template>
<x-cell
:title="label"
:isBordorBootom = 'isBordorBootom'
v-clickoutside="doCloseActive"
:class="[{
'is-textarea': type === 'textarea',
'is-nolabel': !label
}]">
<div class="mint-field">
<textarea
@change="$emit('change', currentValue)"
ref="textarea"
class="mint-field-core"
:placeholder="placeholder"
v-if="type === 'textarea'"
:rows="rows"
:disabled="disabled"
:readonly="readonly"
v-model="currentValue">
</textarea>
<div class="input-waper" v-else >
<div class="prefix">
<slot name="prefix"></slot>
</div>
<input
@change="$emit('change', currentValue)"
ref="input"
class="mint-field-core"
:placeholder="placeholder"
:maxlength="maxlength"
:number="type === 'number'"
:type="type"
:style="`font-size:${fontSize}; paddingLeft:${paddingLeft}`"
@focus="focus"
@blur="$emit('blur')"
:disabled="disabled"
:readonly="readonly"
:value="currentValue"
@keyup="handleKeyup"
@keypress="handleKeypress"
@input="handleInput">
<div
@click="handleClear"
class="mint-field-clear"
v-if="!disableClear"
v-show="(currentValue !== '') && type !== 'textarea' && active">
<!-- <i class="mintui mintui-field-error"></i> -->
<img src="./clean_text_small@3x.png" class="mint-field-clear-img">
</div>
<div class="afterfix">
<slot name="afterfix"></slot>
</div>
</div>
</div>
</x-cell>
</template>
<script>
import XCell from '../../cell/src/cell';
import Clickoutside from '../../../src/utils/clickoutside';
import icon from '../../icon/src/icon'
/**
* mt-field
* @desc 编辑器,依赖 cell
* @module components/field
*
* @param {string} [type=text] - field 类型,接受 text, textarea 等
* @param {string} [label] - 标签
* @param {string} [rows] - textarea 的 rows
* @param {string} [placeholder] - placeholder
* @param {string} [disabled] - disabled
* @param {string} [readonly] - readonly
* @param {string} [state] - 表单校验状态样式,接受 error, warning, success
*
* @example
* <mt-field v-model="value" label="用户名"></mt-field>
* <mt-field v-model="value" label="密码" placeholder="请输入密码"></mt-field>
* <mt-field v-model="value" label="自我介绍" placeholder="自我介绍" type="textarea" rows="4"></mt-field>
* <mt-field v-model="value" label="邮箱" placeholder="成功状态" state="success"></mt-field>
*/
export default {
name: 'CommonField',
data() {
return {
active: false,
currentValue: this.value
};
},
directives: {
Clickoutside
},
props: {
type: {
type: String,
default: 'text'
},
rows: String,
label: String,
placeholder: String,
readonly: Boolean,
disabled: Boolean,
disableClear: Boolean,
state: {
type: String,
default: 'default'
},
value: {},
attr: Object,
isBordorBootom: {
type: Boolean,
default: false
},
fontSize: String,
paddingLeft: String,
maxlength: Number
},
components: { XCell, icon },
methods: {
doCloseActive() {
this.active = false;
},
handleInput(evt) {
this.currentValue = evt.target.value
console.log('handleInput:', evt)
},
handleKeydown(evt) {
console.log('handleKeydown:', evt)
if (this.type === 'number') {
const keyCode = evt.keyCode;
const inputVal = this.$refs.input.value
if (this.isNotNumberKeycode(keyCode) || this.isDotStart(keyCode, inputVal)) {
console.log('组织')
evt.preventDefault();
evt.stopPropagation();
return false;
}
}
},
handleKeypress(evt) {
console.log('handleKeypress:', evt)
if (this.type === 'number') {
const keyCode = evt.keyCode;
const inputVal = this.$refs.input.value
if (this.isNotNumberKeycode(keyCode) || this.isDotStart(keyCode, inputVal)) {
evt.preventDefault();
evt.stopPropagation();
return false;
}
}
},
isBackspace(keyCode) {
return keyCode === 8;
},
isDot(keyCode) {
return keyCode === 46 || keyCode === 190;
},
isNumber(keyCode) {
return (keyCode >= 48 && keyCode <= 57);
},
isDotStart(keyCode, inputVal) {
return this.isDot(keyCode) && (!inputVal || inputVal === '' || /./.test(inputVal));
},
isNotNumberKeycode(keyCode) {
return !this.isBackspace(keyCode) && !this.isDot(keyCode) && !this.isNumber(keyCode);
},
handleKeyup(evt) {
console.log('handleKeyup:', evt)
if (this.type === 'number' || this.type === 'tel') {
let val = evt.target.value
console.log('原来的val:', val)
val = val.replace(/[^d.]/g, '') //清除“数字”和“.”以外的字符
val = val.replace(/.{2,}/g, '.') //只保留第一个. 清除多余的 连续两个
val = val.replace('.', '$#$').replace(/./g, '').replace('$#$', '.') //只保留第一个. 清除多余的 非连续两个
val = val.replace(/^(-)*(d+).(dd).*$/,'$1$2.$3') //只能输入两个小数
if(val.indexOf('.')< 0 && val != ''){ //以上已经过滤,此处控制的是如果没有小数点,首位不能为类似于 01、02的金额
val = parseFloat(val)
}
console.log(val)
// this.currentValue = evt.target.value.replace(/[^d]/g,'');
this.currentValue = val
}
},
handleClear() {
if (this.disabled || this.readonly) return;
this.currentValue = '';
this.$emit('handleClear')
},
focus() {
this.active = true
this.$emit('focus')
}
},
computed: {
iconState() {
if (this.state === 'default') {
return ''
} else if (this.state === 'error') {
return ''
} else if (this.state === 'warning') {
return ''
} else if (this.state === 'success') {
return ''
} else {
return ''
}
},
showClear() {
}
},
watch: {
value(val) {
this.currentValue = val;
},
currentValue(val) {
this.$emit('input', val);
},
attr: {
immediate: true,
handler(attrs) {
this.$nextTick(() => {
const target = [this.$refs.input, this.$refs.textarea];
target.forEach(el => {
if (!el || !attrs) return;
Object.keys(attrs).map(name => el.setAttribute(name, attrs[name]));
});
});
}
}
}
};
</script>
<style lang="scss" scoped>
@import "../../../src/style/var.scss";
input {
font-family: The1Official_Bold ;
}
.mint-field {
100%;
}
.mint-field.is-textarea {
-webkit-box-align: inherit;
-ms-flex-align: inherit;
align-items: inherit;
}
.input-waper {
// border-bottom: 1px solid #EEEEEE;
// padding: .3rem 0 .3rem 0;
border-bottom: 1px solid #A1A5B9;
padding-right: 5px;
display: flex;
// height: 1.1rem;
// padding: .28rem 0rem .27rem 0rem;
position: relative;
box-sizing: border-box;
.mint-field-core {
border: 0;
padding: 0;
display: block;
100%;
resize: none;
box-sizing: border-box;
font-size: .46rem;
outline:none;
color: $color-red;
padding-right: .5rem;
}
.prefix {
display: flex;
flex-direction: column;
justify-content: center;
}
.afterfix {
display: flex;
flex-direction: column;
justify-content: flex-end;
padding-bottom: .4rem;
}
::-webkit-input-placeholder { /* WebKit browsers */
color: $color-red;
opacity: .5;
}
:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
color: $color-red;
opacity: .5;
}
::-moz-placeholder { /* Mozilla Firefox 19+ */
color: $color-red;
opacity: .5;
}
:-ms-input-placeholder { /* Internet Explorer 10+ */
color: $color-red;
opacity: .5;
}
}
// 去除number右侧箭头
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none !important;
margin: 0;
}
.mint-field-clear {
display: flex;
justify-content: center;
align-items: flex-end;
padding-bottom: .4rem;
padding-right: .2rem;
}
.mint-field-clear-img {
14px;
height: 14px;
}
.mint-field-state {
color: inherit;
margin-left: 20px;
}
.mint-field-state .mintui {
font-size: 20px;
}
.mint-field-state.is-default {
margin-left: 0;
}
.mint-field-state.is-success {
color: #4caf50;
}
.mint-field-state.is-warning {
color: #ffc107;
}
.mint-field-state.is-error {
color: #f44336;
}
.mint-field-other {
top: 0;
right: 0;
position: relative;
}
</style>