需求:小程序中手机获取验证码后,自动弹出数字键盘,弹出输入验证码弹窗并且有光标闪烁效果
验证码弹窗组件:
<template>
<van-popup custom-style="border-radius:4px;height:460rpx;top:30%" show="{
{showPayPwdInput}}" close-on-click-overlay="{
{false}}" closeable bind:close="closeFn">
<div class="code">
<div class="input_main">
<div class="input_tip">
<p class="input_tip_tit">输入短信验证码</p>
<span class="input_tip_content">验证码已发送至{
{phoneNumber}}</span>
</div>
<div class="input_row" @tap="$emit('payFocus')">
<div class="pwd_item" v-for="(item,index) in 4" :key="index" :class="{'get_focus':(value_length == index) && payFocus}">
<div v-if="(value_length == index) && payFocus" class="cursor"></div>
<div v-if="(value_length >= index-1)">{
{val_arr[index]}}</div>
</div>
</div>
<p v-if="isSendCode" class="input_code">{
{time}}s后可重新获取</p>
<p v-else class="forget_pwd" @tap="$emit('resend')">重新发送</p>
</div>
</div>
</van-popup>
</template>
<config>
{
"navigationBarTitleText": "授权登录",
"usingComponents": {
"van-popup": "~@vant/popup/index",
}
}
</config>>
<script>
import wepy from '@wepy/core';
wepy.component({
props: {
payFocus: {
type: Boolean,
default: () => {
return []
}
},
val_arr: {
type: Array,
default: () => {
return []
}
},
value_length: {
type: String
},
showPayPwdInput: {
type: Boolean,
default: false
},
phoneNumber: {
type: Number
},
countDown: {
type: Number,
default: 60
}
},
data: {
timeout: null,
time: 60,
isSendCode: true // 是否已经发送验证码
},
watch: {
countDown(val) {
if (val > 0) this.isSendCode = true
if (val === 60) {
this.timeDown(val)
}
}
},
methods: {
closeFn() {
this.$emit('closeFn', this.isSendCode ? this.time : null);
},
// 倒计时
timeDown(time) {
this.timeout = setInterval(() => {
time -= 1;
this.time = time;
if (time <= 0) {
this.time = 60;
this.$emit('timeOver')
this.isSendCode = false;
clearInterval(this.timeout);
}
}, 1000);
}
}
});
</script>
<style lang="less" scoped>
.code {
/* 模拟光标 */
.cursor {
width: 1px;
height: 15px;
background-color: rgba(0,159,252,1);
animation: focus 0.7s infinite;
}
/* 光标动画 */
@keyframes focus {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
}
/* 支付密码框聚焦的时候 */
.get_focus {
border:1px solid rgba(0,159,252,1) !important;
}
.input_code{
text-align: center;
margin: 7rpx auto 7rpx;
font-size: 24rpx;
color: #BFBFBF;
}
/* 支付密码css start */
.input_main {
padding: 19rpx 23rpx;
margin: 0 auto;
width: 570rpx;
height: 394rpx;
background-color: #fff;
z-index: 9999;
border-radius: 4px;
}
.input_tip {
margin: 30rpx;
font-size: 24rpx;
color: #888;
}
.input_tip_tit{
font-size:32rpx;
font-weight:500;
color:rgba(66,68,86,1);
margin-bottom: 18rpx;
}
.input_tip_content{
font-size:28rpx;
font-weight:400;
color:rgba(0,0,0,0.45);
}
/* 密码掩码模拟 */
.input_row {
margin: 64rpx 30rpx;
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
}
.input_row .pwd_item {
margin: 0 10rpx;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 100rpx;
width: 100rpx;
border-radius:4rpx;
border:1px solid rgba(204,204,204,1);
position: relative;
}
.pwd_item:nth-child(1) {
margin-left: 0;
}
.pwd_item:nth-last-of-type(1) {
margin-right: 0;
}
.pwd_item text {
width: 30rpx;
height: 30rpx;
border-radius: 30rpx;
background-color: #555;
}
.forget_pwd {
width: 100rpx;
margin: 7rpx auto 7rpx;
font-size: 24rpx;
color: #009ffc;
}
</style>
关键:在父组件中隐藏一个数字输入框
<template>
<div class="login">
<div class="phoneLogin">
<div class="phoneLogin-phoneInput">
<i class="iconfont icon_jiedian"></i>
<i class="iconfont icon-phone" style="font-size:30px"></i>
<van-field
value="{
{ phoneNumber }}"
bind:input="inputPhone"
maxlength="11"
type="number"
input-class="font32"
clearable
placeholder="请输入手机号"
>
<img slot="left-icon" src="~@/images/icon_phone.png" class="icon_phone" />
</van-field>
</div>
<button
type="primary"
:disabled="isPhone"
class="phoneLogin-next"
:class="{ 'next-disabled' : isPhone }"
@tap="showInputLayer"
>下一步</button>
</div>
<code-panel
@payFocus="payFocus = true"
:value_length="value_length"
:val_arr="val_arr"
ref="codePanel"
@resend="showInputLayer('resend')"
@timeOver="countDown = null"
@closeFn="closeFn"
:showPayPwdInput="showPayPwdInput"
@inputOver="codeFn"
:payFocus="payFocus"
:countDown="countDown"
:phoneNumber="phoneNumber"
/>
<input
:val_arr="val_arr"
:val_length="val_length"
class="input_control"
focus="{
{payFocus}}"
type="number"
bind:blur="payFocus = false"
bind:input="inputPwd"
maxlength="4"
>
</div>
</template>
<script>
import wepy from '@wepy/core';
import { getLogin, sendCode, dologin } from '@/api/login';
import tip from '@/utils/tip';
wepy.page({
data: {
val_arr: [],
value_length: 0,
payFocus: false,
timeout: false, // 是否登录超时
agreementType: 'agreement', // 货六六协议 agreement 用户隐私协议 userAgreement
isFive: true, // 是否过了5秒
confirmTxt: '我知道了',
isviewDeal: false, // 查看协议
isPhone: true,
phoneNumber: '', // 用户手机号
showPayPwdInput: false, // 是否展示验证码输入框
isPhoneLogin: false, // 是否为手机号登录
countDown: null, // 倒计时
backurl: '/pages/personal/index' // 返回上一级
},
onLoad(options) {
if (options.timeout) {
this.timeout = true;
}
},
onShow() {
const app = getApp();
let _url = wx.getStorageSync('backUrl');
if (_url) this.backurl = _url
wx.login({
success (res) {
if (res.code) {
app.appCode = res.code;
} else {
console.log('登录失败!' + res.errMsg)
}
}
})
},
watch: {
value_length(val) {
if (val === 4) {
// 设定延时事件处理
this.codeFn(this.val_arr)
}
}
},
methods: {
inputPwd: function(e) {
// 设置空数组用于明文展现
let val_arr = [];
// 获取当前输入框的值
let now_val = e.$wx.detail.value
// 遍历把每个数字加入数组
for (let i = 0; i < 4; i++) {
val_arr.push(now_val.substr(i, 1))
}
this.val_arr = val_arr;
this.value_length = e.$wx.detail.value.length
},
codeFn(val) {
const app = getApp();
// 验证码输入完成后 执行方法
let params = {
login: Number(this.phoneNumber),
code: Number(val.join('')),
platform: 'mini-program',
type: 1,
mpcode: app.appCode
};
dologin(params, {
'content-type': 'application/x-www-form-urlencoded'
}).then(({ data: { ret, data, msg } }) => {
if (ret === 0) {
this.showPayPwdInput = false;
wx.setStorageSync('userCenter', data);
wx.setStorageSync('token', data.token);
if (this.timeout) {
// 登录超时 返回上个页面
wx.navigateBack();
} else {
wx.reLaunch({ url: this.backurl });
}
} else {
tip.toast(msg, '', 'none');
}
});
},
inputPhone(e) {
let value = e.$wx.detail;
if (value.indexOf(1) === 0 && value.length === 11) {
this.isPhone = false;
} else {
this.isPhone = true;
}
this.phoneNumber = value;
},
showInputLayer(type = '') {
if (
this.phoneNumber === '' ||
this.phoneNumber.length !== 11 ||
this.phoneNumber.indexOf(1) !== 0
) {
tip.toast('手机号格式不正确', '', 'none');
return;
}
if (this.countDown && type !== '') {
this.showPayPwdInput = true;
return;
}
sendCode(
{ mobile: this.phoneNumber },
{ 'content-type': 'application/x-www-form-urlencoded' }
).then(({ data: { ret, data, msg } }) => {
if (ret === 0) {
if (!this.countDown) this.countDown = 60;
this.showPayPwdInput = true;
this.payFocus = true;
} else {
tip.toast(msg, '', 'none');
}
});
},
/**
* 获取焦点
*/
getFocus: function() {
this.payFocus = true;
},
getPhoneNumber(e) {
let self = this;
if (e.$wx.detail.errMsg === 'getPhoneNumber:ok') {
wx.checkSession({
success () {
console.log('登录状态')
self.loginFn(e)
},
fail () {
console.log('重新登录')
// session_key 已经失效,需要重新执行登录流程
wx.login({
success (res) {
if (res.code) {
self.loginFn(e, res.code)
} else {
console.log('登录失败!' + res.errMsg)
}
}
})
}
})
}
}
}
});
</script>
<config>
{
"navigationBarTitleText": "授权登录",
"usingComponents": {
"van-button": "~@vant/button/index",
"van-field": "~@vant/field/index",
"van-icon": "~@vant/icon/index",
"code-panel": "~@/components/code-panel",
"van-popup": "~@vant/popup/index",
'deal': "./deal"
}
}
</config>>
<style lang="less" scoped>
@base: 2rpx;
@import '~@/less/px2rpx.less';
.gray {
color: rgba(191, 191, 191, 1) !important;
}
/* 文本输入框位置: 设置到左边隐藏 */
.input_control {
position: relative;
text-indent: -999em;
left: -100%;
}
.icon_phone{
width:48rpx;
height:48rpx;
margin-right: 16rpx;
}
.font32{
font-size: 32rpx;
}
.login {
.dealBox {
width: 624rpx;
background: rgba(255, 255, 255, 1);
border-radius: 16rpx;
&-confirm {
border-top: 1px rgba(0, 0, 0, 0.09) solid;
height: 96rpx;
display: flex;
align-items: center;
justify-content: center;
color: #009ffc;
}
}
.logo {
width: 308rpx;
display: block;
margin: 126rpx auto 100rpx;
}
padding: 0 48rpx;
.blue {
color: #009ffc;
}
button {
width: 622rpx;
font-size: 36rpx;
font-weight: 400;
letter-spacing: 1px;
}
&-deal {
font-size: 24rpx;
color: #8c8c8c;
text-align: center;
}
&-phone {
background: rgba(255, 255, 255, 1);
border-radius: 4px;
color: #8c8c8c;
margin: 32rpx auto;
}
.phoneLogin {
&-next {
width: 100%;
justify-content: center;
align-items: center;
background: #009FFC !important;
margin: 32rpx auto;
}
.next-disabled {
background-color: #9BDAFF !important;
}
&-phoneInput {
border-bottom: 1rpx #e5e5e5 solid;
}
}
}
</style>
完整代码已附上,需要请自取
也可以扫码体验