最近在做一个项目有个需求,点击输入框,自动调起数字键盘。
最开始一直尝试设置input的type属性,但都遇到了各种各样的问题:
1.type=number,PC和安卓都不能输入非数字字符(注意:安卓仍然可以输入+和-),最大的问题是ios可以切换到非数字键盘,但输入的非数字字符都会自动清空,因此获取不到输入的值,也就没有办法判断输入的值是数字还是非数字
2.type=tel,ios不能切换到非数字键盘,看似完美解决问题,但ios数字键盘没有小数点(.),当需要输入金额时就不适用了
后来产品提出,为了提高用户体验,需要调起一个数字键盘,只能输入正整数和小数,那么type为number和tel都不能满足需求。因为做的是移动端,使用的是vant框架,一看vant正好有数字键盘这个组件,欢天喜地开始使用,但发现并不适用该项目。
该项目有个需求,在第一次填写输入框,提交数据之后,第二次进来要回显前一次填写的数据。我是先在data里面定义了一个对象form_data,在请求获取数据后,把需要回显的数据加在该对象上。
//res.data.input_lists是请求回来的数据,是一个数组,数组里面包对象,要提取的是对象里面的last_val的值
for (let obj of res.data.input_lists) {
this.form_data[obj.alia] = obj.last_val;
}
//这种方式可以实现数据回显,但是vant的数字键盘和input框失去了双向绑定效果
//研究后以为是form_data的属性是在后期加上去,而不是在data里就定义的,所以失去了双向绑定效果
//于是又找了一个解决方案,使用Vue.use(obj,key,value)给form_data设置属性,该方案虽然实现了数字键盘与input框的双向绑定,但是这种方案并不能实现数据回显。
//最后查找是否有遇到同类问题,虽然没有找到,但发现了新的问题,vant的数字键盘组件竟然不兼容ios10!!!而且找了一圈没有找到解决方案
最后,看了大佬的文章(文章链接)后,决定借鉴大佬的方案,在他的基础上,改成适合本项目适用的数字键盘(完美!!!)下面是本项目需求,代码及填坑。
一、需求
1.最多只能输入2位小数(即输入2个小数后就不能输入了)
2.只能输入正确的数字,即第一次输入0,第二次只能输入小数点(.),不能输入数字
3.如果输入的数字最后一位是小数点(.),则点“确认”按钮时自动删除
4.键盘上点击数字时能立即显示到页面上
5.切换input框输入时,需要自动清除该input框之前的数据
二、代码
NumKeypad.vue
// template
<template>
<div class="key-container">
<div class="keyboard" @click.stop="handleKeyPress">
<div class="key-row">
<div class="cell-box">
<div class="key-cell" data-num="7">7</div>
</div>
<div class="cell-box">
<div class="key-cell" data-num="8">8</div>
</div>
<div class="cell-box">
<div class="key-cell" data-num="9">9</div>
</div>
<div class="cell-box">
<div class="key-cell" data-num></div>
</div>
</div>
<div class="key-row">
<div class="cell-box">
<div class="key-cell" data-num="4">4</div>
</div>
<div class="cell-box">
<div class="key-cell" data-num="5">5</div>
</div>
<div class="cell-box">
<div class="key-cell" data-num="6">6</div>
</div>
<div class="cell-box">
<div class="key-cell" data-num></div>
</div>
</div>
<div class="key-row">
<div class="cell-box">
<div class="key-cell" data-num="1">1</div>
</div>
<div class="cell-box">
<div class="key-cell" data-num="2">2</div>
</div>
<div class="cell-box">
<div class="key-cell" data-num="3">3</div>
</div>
<div class="cell-box">
<div class="key-cell" data-num></div>
</div>
</div>
<div class="key-row">
<div class="cell-box">
<div class="key-cell" data-num=".">.</div>
</div>
<div class="cell-box">
<div class="key-cell" data-num="0">0</div>
</div>
<div class="cell-box">
<div class="key-cell key-delete" data-num="D">删除</div>
</div>
<div class="cell-box">
<div class="key-cell" data-num></div>
</div>
</div>
<div class="key-right-clear">
<div class="key-clear" data-num="C">清空</div>
</div>
<div class="key-right-confirm">
<div class="key-confirm" data-num="S">确认</div>
</div>
</div>
</div>
</template>
// less
<style lang="less" scoped>
.key-container {
width: 100%;
height: 3.8rem;
display: flex;
display: -webkit-flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
font-weight: bold;
color: rgba(51, 51, 51, 0.85);
.keyboard {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #fff;
padding: 0.05rem;
background: rgba(240, 240, 240);
box-sizing: border-box;
.key-row {
display: flex;
display: -webkit-flex;
position: relative;
line-height: 1rem;
&::before {
content: "";
position: absolute;
left: 0;
top: 0;
right: 0;
height: 1px;
color: #d5d5d6;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
}
.cell-box {
font-size: 0.42rem;
font-weight: bold;
flex: 1;
-webkit-box-flex: 1;
text-align: center;
position: relative;
padding: 0.05rem;
box-sizing: border-box;
&::after {
content: "";
position: absolute;
overflow: hidden;
top: 0;
right: 0;
bottom: 0;
height: 200%;
color: #d5d5d6;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
&:nth-last-child(1)::after {
border-right: 0;
}
}
.key-cell {
background: #fff;
border-radius: 0.1rem;
}
.key-delete {
font-size: 0.38rem;
}
.key-right-confirm {
font-size: 0.38rem;
padding: 0.05rem;
position: absolute;
text-align: center;
height: 2.1rem;
width: 23%;
line-height: 2.1rem;
background: rgba(240, 240, 240);
z-index: 5;
right: 0.05rem;
bottom: 0.05rem;
}
.key-right-clear {
font-size: 0.38rem;
padding: 0.05rem;
position: absolute;
text-align: center;
height: 2.1rem;
width: 23%;
line-height: 2.1rem;
background: rgba(240, 240, 240);
z-index: 5;
right: 0.05rem;
top: 0.05rem;
}
.key-confirm {
border-radius: 0.1rem;
background: #1561b3;
color: #fff;
}
.key-clear {
border-radius: 0.1rem;
background: #fff;
}
}
}
</style>
效果图
javascript
<script>
export default {
data() {
return {
num: "",
click: null
};
},
props: ["fatherNum", "clickTime"],
mounted() {
this.num = this.fatherNum;
},
methods: {
// 处理按键
handleKeyPress(e) {
const num = e.target.dataset.num;
// 不同的按键处理逻辑
// num为空,直接返回
if (!num) return;
switch (String(num)) {
// 删除键
case "D":
this.handleDeleteKey();
break;
// 清空键
case "C":
this.handleClearKey();
break;
// 确认键
case "S":
this.handleConfirmKey();
break;
default:
this.handleNumberKey(num);
break;
}
},
// 处理删除键
handleDeleteKey() {
this.num = this.fatherNum; // 这一步是为了让this.num能及时接收this.fatherNum的值,解决第二行输入后去删除第一行值出现的bug
const S = this.num;
if (!S) return false;
if (!S.length) return false;// 如果没有输入(或者长度为0),直接返回
// 否则删除最后一个
this.num = S.substring(0, S.length - 1);
this.$emit("deleteEvent", this.num); //把删除处理后了的值返回给父组件
},
// 处理清空键
handleClearKey() {
this.num = ""; // 清空就赋值一个‘’,然后返回给父组件
this.$emit("clearEvent", this.num);
},
// 处理数字
handleNumberKey(num) {
// 确保处理的this.num是父组件传入的this.fatherNum
this.num = this.fatherNum;
this.click = this.clickTime;
const S = this.num; // 拿到父组件点击的那个输入框的值
// 第一次不能输入.
if (this.click == 1) {
if (num == ".") return;
}
//前提是并非刚点击了input
if (this.click != 1) {
// 若this.num有且只有一位数并且是0,则输入的不是.,就返回
if (S && S.length == 1 && S[0] == 0 && num != ".") return;
if (S) {
let index = S.indexOf(".");
// 如果存在.,则不能再次输入.
if (index > -1 && num == ".") return;
// 小数位数不能超过2位
if (S && index > -1 && S.length - 2 > index) return;
}
}
if (!S) {
//如果是首次输入,则原有的this.num是undefined,不需要拼接上
this.num = `${num}`;
} else {
this.num = `${S}${num}`; // 在原有的基础上将点击的那个数字按键的数字加在原有数字字符串末尾,此处是为删除提供数据
}
// 此处不能使用正则表达式判断
this.$emit("numberEvent", num); // 注意传的是点击的那个数字字符串,不是处理后的整个数字字符串
},
// 确认键
handleConfirmKey() {
// 这一步是为了解决一进页面,input框内有值,直接点确认出现数据情况,再次点数据出现undefined问题(如果没有该赋值,this.num为undefined)
this.num = this.fatherNum;
const S = this.num;
// 如果输入的数字最后一位是.,则删除
if (S) {
let index = S.indexOf(".");
if (index == S.length - 1) {
this.num = S.substring(0, S.length - 1);
}
}
this.$emit("confirmEvent", this.num);
}
}
};
</script>
formfill.vue
// template
<!-- input -->
<div class="item" v-for="(obj,index) in inputList" :key="index">
<div class="font32">{{obj.el_name}}</div>
<div class="center-center" @click.stop="numKeyboardUp(obj.alia)">
<div class="num" :class="(obj.alia==diffType&&keyShow)?'selected':'unselected'">
<span v-if="!form_data[obj.alia]" class="font32-normal color45">请输入</span>
<span v-else class="font32 basic-color">{{form_data[obj.alia]}}</span>
</div>
<div class="font32">{{obj.unit}}</div>
</div>
</div>
<!-- 数字键盘 -->
<num-keypad
v-show="keyShow"
:fatherNum="form_data[this.diffType]"
:clickTime="clickTime"
@confirmEvent="confirmEvent"
@numberEvent="numberEvent"
@clearEvent="clearEvent"
@deleteEvent="deleteEvent"
></num-keypad>
// javascript
<script>
import NumKeypad from "@/components/NumKeypad.vue";
export default {
data() {
return {
keyShow: false, // 控制数字键盘显示/隐藏
clickTime: null, //点击input框的次数,1 第一次点击;2 非第一次点击;对应前面的需求5
diffType: "", // 区分点击弹出数字键盘的是哪一行
inputList:[]
};
},
components: {
NumKeypad,
},
methods: {
// ()input点击
numKeyboardUp(alia) {
this.diffType = alia;
this.clickTime = 1;
this.keyShow = true;
},
// 数字键盘点击事件
// 点击数字键
numberEvent(res) {
this.form_data[this.diffType] =
this.clickTime == 1
? `${res}`
: `${this.form_data[this.diffType]}${res}`;
// 此处设置是为了让键盘点击的值能立马显示在input框中,如没有此设置,只有点击确认键盘隐藏时才会显示数据
this.keyShow = false;
this.keyShow = true;
this.clickTime = 2;
},
// 点击清除键
clearEvent(res) {
this.form_data[this.diffType] = res; // 清除,子组件传过来的是‘’
// 此处设置是为了让键盘点击的值能立马显示在input框中,如没有此设置,只有点击确认键盘隐藏时才会显示数据
this.keyShow = false;
this.keyShow = true;
},
// 点击删除键
deleteEvent(res) {
this.form_data[this.diffType] = res;
// 此处设置是为了让键盘点击的值能立马显示在input框中,如没有此设置,只有点击确认键盘隐藏时才会显示数据
this.keyShow = false;
this.keyShow = true;
},
// 点击确认键
confirmEvent(res) {
this.form_data[this.diffType] = res;
this.keyShow = false; // 点击确认,收起键盘
},