项目中用到了,有一些难点, 重新实现,记录一下
1.代码
template
<template>
<div class="container">
<div class="bind-phone">
<div class="bind-phone-title">{{ title }}</div>
<div class="bind-phone-content">
<swiper :options="swiperOption" ref="swiper">
<swiper-slide>
<div class="choose-area">
<div class="choose-area-item" @click="selectItem(item, index)"
v-for="(item, index) in list" :key="index">
<div class="left">
<img :src="require(`../assets/img/${currenCodeIndex === index ?
'selected': 'unselect'}.png`)"> <span>{{ item.area }}</span>
</div>
<div class="right">{{ item.code }}</div>
</div>
</div>
</swiper-slide>
<swiper-slide>
<div class="input-hone-number-content">
<div class="bind-phone-input-number">
<div class="select-code" @click="selectCode">
<span>{{ currentCode }} </span><img src="../assets/img/arrow.png">
</div>
<div class="input-box">
<input
type="text"
v-model="phone"
placeholder="输入手机号"
autofocus='autofocus'
>
<img src="../assets/img/close.png" v-if="phone.length >= 8"
@click="phone = ''">
</div>
</div>
<div :class="['get-verify-code', { 'get-verify-code-available':
phone.length >= 8}]" @click="getVerifyCode">
获取验证码
</div>
</div>
</swiper-slide>
<swiper-slide>
<div class="verify-code-content">
<div class="input-box">
<div :class="['input-box-item', {'input-box-item-active':
+verifyCode.length === item -1} ]" v-for="item in 6" :key="item">
{{ verifyCode.length >= item ? verifyCode[item - 1] : '' }}
</div>
<input
class="input-verify-code"
type="text"
v-model="verifyCode"
autofocus='autofocus'
ref="inputVerifyCode"
inputMode="numeric"
autoComplete="one-time-code"
maxLength="6"
pattern="[0-9]*"
:class="`${ isAnd ? '': 'hide-cursor'}`"
>
</div>
<div class="verify-code-status">验证码错误</div>
<div class="confirm-verify-code" @click="getVerifyCode">{{ timeLeft ?
`重新发送 (${timeLeft}s)` : '重新发送'}}</div>
</div>
</swiper-slide>
</swiper>
</div>
</div>
</div>
</template>
js
<script>
export default {
data() {
return {
swiperOption: {
loop: false,
allowTouchMove: true,
initialSlide: 1,
},
currentCode: '+86',
currenCodeIndex: 0,
phone: '',
title: '绑定领奖手机号',
list: [
{ area: '中国' , code: '+86'},
{ area: '印尼', code: '+84'}
],
verifyCode: '',
timeLeft: 0, // 60秒倒计时
verifyCodeText: '验证码已发送'
}
},
methods: {
focusInput() {
this.$refs.inputVerifyCode.focus();
},
selectCode() {
this.$refs.swiper.swiper.slidePrev();
},
selectItem(item, index) {
if (this.selected) return
this.selected = true // 选中后短时间内,300ms不能切换
this.currentCode = item.code
this.currenCodeIndex = index
setTimeout(() => {
this.$refs.swiper.swiper.slideNext();
this.selected = false
}, 300)
},
getVerifyCode() {
// 第二个getVerifyCode如果在倒计时不能进行发送
if (this.timeLeft) return
this.timeLeft = 60
// 开始调用获取验证码的接口,成功之后
// 倒计时
this.countDown();
this.$refs.swiper.swiper.slideNext();
setTimeout(()=>{
// 首次加载的时候input-verify-code会focus,但是切换的时候并没有加载需要手动focus
this.focusInput();
},200)
},
countDown() {
clearTimeout(this.countDown.timer);
if (this.timeLeft > 0) {
this.countDown.timer = setTimeout(() => {
this.timeLeft--;
this.countDown();
}, 1000);
}
},
// 检验验证码是否正确
checkVerifyCode() {
if(this.checkVerifyCode.requesting) return ;
this.checkVerifyCode.requesting = true;
// 数据请求
// 如果成功刷新页面的绑定的接口,显示绑定的状态
// 如果不成功
// this.verifyCodeText = '验证码错误'
// promise.finally之后 this.checkVerifyCode.requesting = false
}
},
computed:{
isAnd() {
return navigator.userAgent.indexOf('Android') > -1 || navigator.userAgent.indexOf('Adr') > -1
}
},
watch: {
// 如果大于11个数,后面的不能输入
phone(val) {
if (!/^\d{0,11}$/.test(val)) {
this.$nextTick(() => {
this.phone = val.replace(/\D/g, "").slice(0, 11);
});
}
},
// 通过对verifyCode的监控来判断用户输入的是否正确,如果大于6,只获取前6
verifyCode(val) {
// 如果是6个直接请求
if (/^\d{6}$/.test(val)) {
// 直接请求
this.checkVerifyCode();
return;
}
// 如果大于6个变成6个,还会触发一次上面的请求
if (!/^\d{0,6}$/.test(val)) {
this.$nextTick(() => {
this.verifyCode = val.replace(/\D/g, "").slice(0, 6);
});
}
},
}
}
</script>
css
<style lang="scss" scoped>
.container {
display: flex;
align-items: center;
justify-content: center;
background: pink;
width: 100%;
height: 100vh;
}
.bind-phone {
width: 315px;
height: 284px;
background: #FFFFFF;
border-radius: 15px;
overflow: hidden;
.bind-phone-title {
margin-top: 40px;
}
.bind-phone-content {
.input-hone-number-content {
margin-top: 40px;
display: flex;
flex-direction: column;
align-items: center;
.bind-phone-input-number {
width: 275px;
height: 50px;
background: #F8FAFB;
border-radius: 28px;
overflow: hidden;
display: flex;
align-items: center;
.select-code {
text-align: left;
margin-left: 20px;
width: 100px;
display: flex;
align-items: center;
img {
width: 15px;
height: 15px;
vertical-align: middle;
}
span {
vertical-align: middle;
}
}
.input-box {
position: relative;
}
.input-box img {
position: absolute;
right: 30px;
top: 6px;
width: 16px;
height: 16px;
}
input {
background: #F8FAFB;
outline:none; // 去掉border样式1
caret-color:#171717; // 光标的颜色
height: 24px;
border: none; // 去掉border样式2
font-size: 17px;// 光标的大小
}
input::input-placeholder {
color: #2C2C2C; // 设定placeholder颜色
}
}
.get-verify-code {
width: 275px;
height: 50px;
background: #F8FAFB;
border-radius: 30px;
text-align: center;
line-height: 50px;
margin-top: 30px;
color: #DFE5E9;
}
.get-verify-code-available {
background-color: pink;
color: black;
}
}
.choose-area-item {
display: flex;
padding: 0px 30px;
height: 40px;
line-height: 40px;
margin-top: 40px;
.left {
flex: 1;
text-align: left;
img {
vertical-align: middle;
width: 18px;
height: 18px;
}
span {
vertical-align: middle;
}
}
}
.verify-code-content {
margin-top: 40px;
padding: 0px 20px;
position: relative;
.input-box {
display: flex;
justify-content: space-between;
.input-box-item {
border-bottom: 2px solid #F2F3F6;
width: 34px;
height: 30px;
position: relative;
user-select: none;
font-size: 20px;
&.input-box-item-active:after{
position: absolute;
top: -3px;
left: 2px;
content: "";
width: 2px;
height: 25px;
background: pink;
border-radius: 1px;
animation: cursor 0.9s infinite;
}
}
.input-verify-code {
outline:none; // 去掉border样式1
border: none; // 去掉border样式2
position: absolute;
top: -25px;
left:18px ;
height: 25px;
width: 280px;
background-color: transparent;
color: transparent;
-webkit-tap-highlight-color: rgba(0,0,0,0); /*点击高亮的颜色*/
}
}
.confirm-verify-code {
width: 275px;
height: 50px;
background: pink;
border-radius: 30px;
text-align: center;
line-height: 50px;
}
.hide-cursor {
text-indent: -999em; /*文本向左缩进*/
margin-right: -100%; /*输入框光标起始点向右移*/
width: 200%; /*输入框增大一倍*/
opacity: 0;
}
.verify-code-status {
margin-top: 15px;
}
.confirm-verify-code {
margin-top: 30px;
}
}
@keyframes cursor {
0%,
100% {
opacity: 0;
}
50% {
opacity: 1;
}
}
}
}
</style>
2.实现效果
3.要注意的点
1.验证码的框通过6个div实现,输入操作通过无边框的input实现,对输入的数据做6个数字的处理,按照 verifyCode.length >= item ? verifyCode[item - 1] : ''的规则填入到div中
2.切换到输入验证码页面应该进行focus操作
3.ios上光标的兼容性问题
4.输入验证码调接口,通过watch来实现
如何引入移动端swiper,参考swiper,vue-awesome-swiper在移动端的使用
有问题一起交流哈~