5.3
5.3.0 写在前面
之前写扫码登录的时候对于什么时候clear那个发送请求获取二维码当前状态的定时器
timer
,只考虑了两种情况,现在再完善一下,再添加一种情况,就是路由的跳转:
- 关闭
Dialog
弹窗- 二维码状态 :
失效
或者登录成功/失败
- 路由的跳转 :只要是从 /logincode 跳转出去,就应该清理 timer
那么如何实现路由跳转出去清理当前组件里的 timer 呢?
刚开始我的想法是:
在父组件中设置变量
codeVisible
,并且将其传递给子组件在他们的父组件中监听
$route
,当路由发生跳转的时候改变codeVisible
的值子组件的定时器中判断
codeVisible
,来决定是否清理 timer// 监听路由变化 // 当路由从 /logincode 跳转到 /loginphone 或/register 将loginVisible变成 false watch: { '$route' (to, from) { console.log(from.path) // 下面的数据传送到子组件会有延迟 // if (from.path === '/logincode') { // console.log('从扫码页跳转出去了') // this.codeVisible = false // } } }
但是!我发现这样的做法不起作用,使用Vue的浏览器工具我发现在父组件的值发生变化的时候,子组件的值还没有来得及变化,然后会有一个延迟,此时子组件已经被移除,跳转到了新的路由,然后timer还是没有清理掉。
最后我在扫码登录组件的 beforeDestroy
周期,在销毁该组件之间将 codeVisible 置为 false,timer 也就清理掉了
beforeDestroy () {
// 当前组件被销毁之前 将codeVisible设置为false 用于消除定时器
this.codeVisible = false
}
5.3.1 UI界面
5.3.2 页面逻辑分析和实现
半角二维码
- 点击跳转到扫码登录的路由 => 路由跳转
- hover 提示信息:扫码登录更安全
这里的提示信息使用的是组件库的
el-tooltip
,对位置稍作了调整。
<!-- 左上角半角二维码 -->
<div id="halfQR-container">
<el-tooltip
class="item"
effect="dark"
content="扫码登录更安全"
placement="right-start"
:offset="-110"
>
<router-link to="/logincode">
<el-image
style="width: 50px; height: 50px;"
:src="halfQRUrl"
fit="contain"
></el-image>
</router-link>
</el-tooltip>
</div>
IMG
使用了组件库的
el-image
,调整了一下位置
<!-- 图片 -->
<div class="block" style="margin:20px 0">
<el-image
fit="cover"
style="width: 70%;position: relation; left: 50%; transform: translateX(-50%)"
:src="phoneImgUrl"
>
<div slot="placeholder" class="image-slot">
加载中<span class="dot">...</span>
</div>
</el-image>
</div>
表单
-
表单添加了验证,对电话号码添加了正则表达式来验证是否有效
-
当电话号码有效时,后面的获取验证码按钮才会取消 disabled ,否则无法发送验证码,主要是监听手机号
watch: { // 监听手机号 改变获取验证码按钮状态 'ruleForm.phoneNumber': function (value) { const reg = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/ const val = reg.test(value) if (val) { this.attcode = false } else { this.attcode = true } } }
-
点击发送验证码按钮,该按钮消失,取而代之的是 60s 的倒计时
// 发送验证码 async getCode () { // 调用 sendCode 发送验证码 const sendCodeData = await sendCode(this.ruleForm.phoneNumber) if (sendCodeData.code !== 200) this.successSendMsg() // 修改页面样式 const timer = setInterval(() => { this.codeSec = this.codeSec - 1 this.codeMsg = this.codeSec + 's后重试' this.showBtn = false if (this.codeSec === 0) { clearInterval(timer) this.codeSec = 60 this.showBtn = true } }, 1000) }
-
-
验证码也添加了正则表达式来验证是否是四位
-
监听密码,当4位的时候自动请求并且登录
watch: { 'ruleForm.phoneCode': async function (code) { if (code.length === 4) { // 自动请求并且登录 const { data: byCodeData } = await byCode(this.ruleForm.phoneNumber, this.ruleForm.phoneCode) console.log(byCodeData) if (byCodeData.code === 200) { this.successLoginMsg() // 保存信息到 Vuex 跳转页面 } } } }
奇奇怪怪
当验证码失败的时候我收到了状态码为503的返回数据,成功的时候是200,主要是这个503控制台报错让我十分难受
登录成功后(扫码和手机号)
- 登录成功后打算将账户信息都存放在 Vuex 中
- 由于扫码和手机号验证码端(之后别的组件也有可能要使用)都会使用到相同的方法来更新Vuex中的state数据,于是打算学习一下 Mixin 混入来达到代码更好的复用。
- 待更…
5.3.3 源码
github地址:CloudMusic-vue2
LoginByPhoneNumber.vue
<template>
<div id="login-phone-container">
<!-- 左上角半角二维码 -->
<div id="halfQR-container">
<el-tooltip
class="item"
effect="dark"
content="扫码登录更安全"
placement="right-start"
:offset="-110"
>
<router-link to="/logincode">
<el-image
style="width: 50px; height: 50px;"
:src="halfQRUrl"
fit="contain"
></el-image>
</router-link>
</el-tooltip>
</div>
<!-- 图片 -->
<div class="block" style="margin:20px 0">
<el-image
fit="cover"
style="width: 70%;position: relation; left: 50%; transform: translateX(-50%)"
:src="phoneImgUrl"
>
<div slot="placeholder" class="image-slot">
加载中<span class="dot">...</span>
</div>
</el-image>
</div>
<!-- 输入框 -->
<div id="form-container" style="margin:10px">
<!-- <el-input placeholder="请输入手机号" v-model="phoneNumber" class="input-with-select">
<i slot="prefix" class="el-input__icon el-icon-mobile-phone"></i>
</el-input> -->
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="auto" class="login-ruleFrom" style="margin:0 15px">
<el-form-item label="" prop="phoneNumber">
<el-input v-model="ruleForm.phoneNumber" placeholder="请输入手机号">
<i slot="prefix" class="el-input__icon el-icon-mobile-phone"></i>
</el-input>
<el-button
size="mini"
class="getCodeButton"
:disabled="attcode"
v-if="showBtn"
@click="getCode"
>获取验证码</el-button>
<el-button
class="getCodeButton"
plain
disabled
v-else
>{{codeMsg}}</el-button>
</el-form-item>
<el-form-item label="" prop="phoneCode">
<el-input v-model="ruleForm.phoneCode" placeholder="请输入验证码">
<i slot="prefix" class="el-input__icon el-icon-lock"></i>
</el-input>
</el-form-item>
<el-form-item label="" prop="type" style="margin-top:-10px">
<el-switch v-model="ruleForm.autoLogin" active-text="自动登录" ></el-switch>
</el-form-item>
<el-form-item style="margin-bottom:-20px">
<el-button type="primary" @click="submitForm('ruleForm')" style="width:100%;">登录</el-button>
<router-link to="/register" class="other-way"> 注册 </router-link>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import { byCode, sendCode } from '@/api/LoginAndRegister/loginByPhone.js'
export default {
data () {
return {
// 获取验证码按钮是否禁用
attcode: true,
// 获取验证码按钮是否展示
showBtn: true,
codeMsg: '获取验证码',
// 倒计时
codeSec: 60,
halfQRUrl: require('@/assets/images/halfQR.png'),
phoneImgUrl: require('@/assets/images/phoneImg.png'),
ruleForm: {
phoneNumber: '',
phoneCode: '',
autoLogin: false
},
rules: {
phoneNumber: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/, message: '请正确填写您的手机号码', trigger: 'blur' }
],
phoneCode: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{ pattern: /^[0-9]{4}$/, message: '请填写有效的验证码', trigger: 'blur' }
]
}
}
},
watch: {
// 监听手机号 改变获取验证码按钮状态
'ruleForm.phoneNumber': function (value) {
const reg = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/
const val = reg.test(value)
if (val) {
this.attcode = false
} else {
this.attcode = true
}
},
'ruleForm.phoneCode': async function (code) {
if (code.length === 4) {
// 自动请求并且登录
const { data: byCodeData } = await byCode(this.ruleForm.phoneNumber, this.ruleForm.phoneCode)
console.log(byCodeData)
if (byCodeData.code === 200) {
this.successLoginMsg()
// 保存信息到 Vuex 跳转页面
}
}
}
},
methods: {
// 错误提示信息
errorMsg () {
this.$message({
showClose: true,
message: '电话或验证码错误!',
type: 'error'
})
},
// 短信发送成功提示信息
successSendMsg () {
this.$message({
showClose: true,
message: '短信发送成功!',
type: 'success'
})
},
// 登录成功提示信息
successLoginMsg () {
this.$message({
showClose: true,
message: '登录成功!',
type: 'success'
})
},
// 提交登录表单
async submitForm (formName) {
console.log(this.ruleForm.phoneNumber, this.ruleForm.phonePassword, this.ruleForm.autoLogin)
this.$refs[formName].validate(async (valid) => {
if (valid) {
// 发送请求
const byCodeData = await byCode(this.ruleForm.phoneNumber, this.ruleForm.phoneCode)
if (byCodeData.code === 200) {
this.successLoginMsg()
// 保存信息到 Vuex 跳转页面
} else {
this.errorMsg()
}
} else {
console.log('error submit!!')
return false
}
})
},
// 发送验证码
async getCode () {
// 调用 sendCode 发送验证码
const sendCodeData = await sendCode(this.ruleForm.phoneNumber)
if (sendCodeData.code !== 200) this.successSendMsg()
// 修改页面样式
const timer = setInterval(() => {
this.codeSec = this.codeSec - 1
this.codeMsg = this.codeSec + 's后重试'
this.showBtn = false
if (this.codeSec === 0) {
clearInterval(timer)
this.codeSec = 60
this.showBtn = true
}
}, 1000)
}
}
}
</script>
<style lang="less" scoped>
#halfQR-container{
position: absolute;
top: 0;
left: 0;
}
.el-select{
width: 90px;
}
.input-with-select .el-input-group__prepend {
background-color: #fff;
}
.other-way{
display: inline-block;
margin: 10px 0 25px 0;
width: 100%;
text-align: center;
padding-right: 0;
font-size: 14px;
line-height: 28px;
color: rgba(0,0,0,0.80);
text-decoration-line:underline;
}
.getCodeButton{
box-sizing: border-box;
position: absolute;
height: 100%;
right: 0;
border-radius: 0;
}
</style>