一. 前言
先看下使用场景效果图:
- 点击输入框唤起键盘,蓝框就相当于input的光标,验证码输入错误或者不符合格式要求会将字体以及边框改成红色提示,持续1s,然后清空数据,恢复原边框样式;
- 5位验证码输入完毕,点击页面其他位置,隐藏键盘;这时如果发现验证码有误,再次点击输入框又唤起键盘,也能正常删除数字(这里其实做的时候遇到了bug,再次聚焦不能删除错误数字,下文会讲到)。
二. 实现思路
具体实现思路:
- 将input标签相对于父元素做绝对定位,与父元素左边距设置为负的本身宽度即可(position: absolute; top: 0; left:-100%; width: 100%; height: 100%;)。
- 动态去设置input的focus属性。
- input同级使用for循环去创建5个正方形的view标签。
- 给input同级创建的view标签绑定点击事件,在点击事件方法实现中去设置input的focus属性为true,即可弹出键盘。
- 在键盘输入的时候,即可触发input属性的一系列方法,利用v-model双向绑定,将input输入的值赋值给循环的view方框即可。
这样input也就不在屏幕中,但是又可以触发input的事件。
总的来说就是,使用for循环去创建5个正方形的view标签,然后创建一个input标签,type=tel,最大输入长度为5(根据需求来设置),再将input伪隐藏掉,获取的值分别放到5个view中展示。
验证码失败后利用v-model双向绑定,清空输入的值,增加错误提示文字和边框样式。
三. 代码实现
父组件
<uni-popup ref="codeInputPopup" background-color="#fff" :mask-click ="false" type="center">
<CodeInput
:codeLength="5"
:disabled="codeBtnDisabled"
@codeInputClose="codeInputClose"
@submitGoodCode="submitGoodCode"
/>
</uni-popup>
<script>
export default {
data() {
return {
intviation_code:'', //邀请码
codeBtnDisabled: false //防止接口请求还未返回数据,用户多次点击
}
},
methods: {
// 提交邀请码
async submitGoodCode(intviation_code){
this.codeBtnDisabled = true
this.intviation_code = intviation_code
const response = await this.$api.post('/ebapi/pink_api/secret_intviation_check', {
code: intviation_code
})
if(response.code === 200){
this.codeBtnDisabled = false
this.$refs.codeInputPopup.close()
}else{
this.codeBtnDisabled = false
this.$refs.codeInputPopup.close()
this.$api.msg(response.msg)
}
},
codeInputClose(){
this.$refs.codeInputPopup.close()
this.codeBtnDisabled = false
}
}
</script>
子组件
<template>
<view>
<view class="code-popup-top">
<view class="code-title">请输入商品邀请码</view>
<view class="close-icon" @click="codeInputClose">
<uni-icons type="closeempty" size="30" color="#999999" />
</view>
</view>
<!-- 错误提示 -->
<view class="code_errow" v-if="codeColor == '#ff0000'&& !isNum">邀请码必须{{ codeLength }}位数</view>
<view class="code_errow" v-if="codeColor == '#ff0000'&& isNum ">邀请码必须是数字</view>
<view class="code_input_con">
<view
v-for="(item, index) in codeLength"
:key="index"
class="code_input_item"
:style="(index == intviation_code.length? 'border: 5rpx solid #1195db; width: 88rpx; height: 88rpx; line-height: 80rpx;':'color: ' + codeColor + ';' +'border: 2rpx solid' + codeColor)"
@click="focus = true"
>{{ intviation_code[index] && intviation_code[index] || '' }}</view>
<input
class="cinput"
type="tel"
v-model="intviation_code"
:maxlength="codeLength"
:focus="focus"
:cursor="intviation_code.length"
@focus="focus = true "
@blur="focus = false"
/>
</view>
<button
:class="['submit_code_btn', disabled ? 'btn_disabled' : '']"
:disabled="disabled"
@click="submitGoodCode"
>确定</button>
</view>
</template>
<script>
export default {
data() {
return {
codeColor: '#313131', //自定义错误码颜色
intviation_code: '', //用户输入的验证码
focus: false, // 动态获取焦点的值
isNum: false,
}
},
props: {
codeLength: {
type: Number,
default: 5,
},
disabled: {
type: Boolean,
default: false,
},
},
methods: {
codeInputClose() {
this.intviation_code = ''
this.$emit('codeInputClose')
},
submitGoodCode() {
if (this.intviation_code.length === this.codeLength) {
if (Number(this.intviation_code)) {
this.$emit('submitGoodCode', this.intviation_code)
} else {
this.isNum = true
this.publicErrorSetting()
}
} else {
this.publicErrorSetting()
}
},
// 输入不符合规范,更改样式并清空
publicErrorSetting() {
this.codeColor = '#ff0000'
setTimeout(() => {
this.intviation_code = ''
this.codeColor = '#313131'
this.isNum = false
}, 1000)
},
},
}
</script>
<style lang="scss" scoped>
.code-popup-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 50upx;
.code-title {
font-size: 34upx;
color: #333;
font-weight: bold;
position: relative;
&::before {
content: '';
position: absolute;
bottom: 0;
width: 40upx;
height: 19upx;
background: linear-gradient(
to right,
rgba(57, 181, 74, 1),
rgba(57, 181, 74, 0.1)
);
}
}
.close-icon {
background: #f2f4f7;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
}
.code_errow {
font-size: 30upx;
color: #ff5500;
margin-bottom: 20upx;
}
.submit_code_btn {
width: 100%;
height: 83upx;
line-height: 83upx;
border-radius: 7upx;
background: #39b54a;
color: #fff;
font-size: 31upx;
text-align: center;
margin-top: 45upx;
}
.btn_disabled {
color: rgba(255, 255, 255, 0.5) !important;
background-color: rgba(57, 181, 74, 0.4) !important;
}
.code_input_con {
display: flex;
justify-content: space-around;
position: relative;
.code_input_item {
margin-left: 10upx;
text-align: center;
line-height: 88upx;
border-radius: 14upx;
width: 88upx;
height: 88upx;
font-size: 60upx;
font-weight: bold;
color: #333;
&:last-child {
margin-right: 0;
}
}
/*input隐藏掉*/
.cinput {
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
}
}
</style>
四. 过程中遇到的问题
1)input 的type=‘number’, ios手机正常,光标在内容最后,但Android手机光标有时候在内容最前面,导致聚焦内容删不掉。
修改input 的type = 'tel'
,:cursor="intviation_code.length"
, 这样cursor属性才生效,并指定focus时光标的位置在内容最后;
type=‘tel’,也会有个小问题,可以输入一些字符,但是我们的需求只能是数字,所以代码中要做限制。就能解决这个问题了。
这个cursor无效的问题,在h5模式应该是type的原因,我试了在type是number或digit时cursor就无效,text、tel、idcard就有效
2)还有另外一种方法
- 设置input的type=“number”,就不需要设置光标位置了;然后隐藏input文字和光标,相当于间接隐藏了input框;
- 用到了css样式设置,
color: transparent; caret-color: transparent;
- 最主要的还是相对于父元素做绝对定位,与父元素左边距设置为负的本身宽度的一半即可(position: absolute; top: 0; left:-100%; width: 200%; height: 100%;)with: 200%为了增大点击区域,解决Android机型再次唤起键盘不能聚焦,删不掉错误数字的问题。
张鑫旭的CSS改变插入光标颜色caret-color简介及其它变色方法
自我测试CSS : caret-color
<template>
<view>
<input
class="cinput"
type="number"
v-model="intviation_code"
:maxlength="codeLength"
:focus="focus"
@focus="focus = true "
@blur="focus = false"
/>
</view>
</view>
</template>
<script>
export default {
data() {
return {
intviation_code: '', //商品邀请码
focus: false,
}
},
methods: {}
</script>
<style lang="scss" scoped>
.cinput {
position: absolute;
top: 0;
left: -100%;
width: 200%;
height: 100%;
color: transparent; //输入文字颜色透明
caret-color: transparent !important; //改变插入光标颜色为透明
}
}
// 考虑兼容性
// 浏览器支持caret-color属性,优先使用caret-color(Chrome/Firefox/Opera);其次使用::first-line方法(Safari);最后忽略(如IE)。
@supports (-webkit-mask: none) and (not (caret-color: transparent)) {
.cinput {
color: transparent !important;
}
.cinput::first-line {
color: transparent !important;
}
}
</style>
还可参考:
6位验证码输入框、隐藏光标、letter-spacing失效、自定义光标,光标动画
uniapp 手机验证码输入框(随机数、倒计时、隐藏手机号码中间四位)可以直接使用