目录
一、登录模块布局
1.1、模态窗口的书写
点击登录按钮,弹出模态窗口,如下图:
在cpmonents目录下新建一个LoginBar.vue组件
<template>
<div class="modal">
<div class="mask"></div>
<div class="login-box">
</div>
</div>
</template>
<script>
export default {}
</script>
<style lang = "less" scoped>
.modal{
position: fixed;
width: 100%;
height: 100%;
left: 0;
top: 0;
/* bottom: 0;
right: 0; */
.mask{
width: 100%;
height: 100%;
background-color: rgba(0,0,0,.5);
}
.login-box{
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
margin:auto;
width: 555px;
height: 423px;
background: url("../assets/img/login.png");
}
}
</style>
然后在App.vue组件中引入,注册,使用。
1.2、设置点击展示模态窗口
因为项目中可能有在各个组件中触发这个模态窗口的展示,所以控制模态框展示的变量可以放在Vuex中
在store中新建showModal目录,并在其中新建index.js:
export default{
namespaced:true,
// 以上的方式使其成为带命名空间的模块。保证在变量名一样的时候,添加一个父级名拼接。
state:{
isShowLoginModal:false//表示是够展示登录窗口
},
mutations:{
// 修改isShowLoginModal的值
changeIsShowLoginModal(state,payload){
state.isShowLoginModal=payload
}
},
actions:{
},
}
扩展:namespaced:true
使用模块中的mutations、getters、actions时要加上模块名,例如使用commint执行mutations时
格式:模块名/模块中的mutations
xxx/setUserInfo
this.$store.commit(“userInfo/setUserInfo”,userInfo)
获取属性时同样加上模块名
格式:store.state.模块名.模块属性
$store.state.userInfo.userName
在store.js中引入:
import Vue from 'vue'
import Vuex from 'vuex'
import showModal from "./showModal"
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
showModal
}
})
LoginBar.vue组件中:
<script>
import {mapState} from "vuex"
export default {
computed:{
...mapState({
isShowLoginModal:state=>state.showModal.isShowLoginModal
})
}
}
</script>
HeaderBar.vue组件中:
<ul>
...
<li class="login-btn" @click="chanIsShowLoginModal(true)">登录</li>
</ul>
<script>
import {mapMutations} from "vuex"
export default {
data () {},
methods:{
...mapMutations({
chanIsShowLoginModal:"showModal/chanIsShowLoginModal"
})
}
}
</script>
1.3、点击关闭模态窗口
LoginBar.vue中:
<template>
<div class="modal" v-show="isShowLoginModal">
<!--点击遮罩层也可以关闭模态窗口-->
<div class="mask" @click="chanIsShowLoginModal(false)"></div>
<div class="login-box">
<div class="close" @click="chanIsShowLoginModal(false)"></div>
</div>
</div>
</template>
<script>
import {mapState,mapMutations} from "vuex"
export default {
...
methods:{
...mapMutations({
chanIsShowLoginModal:"showModal/chanIsShowLoginModal"
})
}
}
</script>
<style lang = "less" scoped>
.modal{
...
.login-box{
...
.close{
width: 22px;
height: 22px;
background: url("../assets/img/close.png");
position: absolute;
right: 60px;
top: 16px;
cursor: pointer;
}
}
}
</style>
1.4、点击标题栏的切换效果
LoginBar.vue中:
:class="isShowForm == true ? 'active' : ' ' "《等同》:class="{active:isShowForm}"
<ul class="title">
<li @click="isShowForm=true" :class="isShowForm == true ? 'active' : ''">手机号码登录</li>
<li style="margin:0 10px">|</li>
<li @click="isShowForm=false" :class="{active:!isShowForm}">微信扫码登录</li>
</ul>
<div class="body">
<div class="form" v-show="isShowForm">
表单
</div>
<div class="qrcode" v-show="!isShowForm">
二维码
</div>
</div>
<script>
export default {
data () {
return {
isShowForm:true
}
}
...
}
</script>
<style lang = "less" scoped>
.modal{
...
.login-box{
...
.title{
display: flex;
justify-content: center;
width: 100%;
padding-top: 50px;
font-size:20px;
color:#999;
.active{
color:#333;
}
}
.body{
width: 355px;
margin:20px auto 0;
height:200px;
}
}
}
</style>
1.5、表单基本布局
LoginBar.vue中:
<div class="body">
<div class="form" v-show="isShowForm">
<div class="mb20 row">
<input type="text" class="ipt" placeholder="请输入手机号" v-model="phoneNum">
</div>
<div class="mb20 row">
<input type="text" class="ipt" placeholder="请输入短信验证码">
<div class="btn checkcode-btn">获取验证码</div>
</div>
<div class="mb20 btn">
登录
</div>
</div>
<div class="qrcode" v-show="!isShowForm">
二维码
</div>
</div>
<style>
...
.title{
display: flex;
justify-content: center;
width: 100%;
padding-top: 50px;
font-size:20px;
color:#999;
.active{
color:#333;
}
}
.body{
width: 355px;
margin:20px auto 0;
height:200px;
.form{
.row{
flex:1;
display: flex;
}
.ipt{
box-shadow: 0;
flex:1;
height: 48px;
border: 1px solid #e4e7eb;
}
.checkcode-btn{
width: 100px;
margin-left: 10px;
}
.btn{
background: #0a328e;
color: #fff;
text-align: center;
height: 50px;
line-height: 50px;
cursor: pointer;
}
}
}
</style>
二、拼图验证滑块
插件参考:vue-monoplasty-slide-verify: 基于滑动式的验证码,免于字母验证码的繁琐输入用于网页注册或者登录
2.1、安装插件
npm install --save vue-monoplasty-slide-verify
或者
yarn add vue-monoplasty-slide-verify
2.2、main.js入口文件引中入
import SlideVerify from 'vue-monoplasty-slide-verify' // 拼图验证码
Vue.use(SlideVerify)
2.3、在组件中使用
<template>
<slide-verify :l="42" :r="20" :w="362" :h="140" @success="onSuccess" @fail="onFail" @refresh="onRefresh" :style="{ width: '100%' }" class="slide-box" ref="slideBlock" :slider-text="msg"></slide-verify>
</template>
样式和函数参照文档复制即可。
2.4、点击登录按钮,判断是否有进行拼图滑块验证
登录之前,我们需要验证用户是否有拼图验证过,有拼图验证过才可以登录。
我们以msg文字内容来判断是否有进行拼图滑块验证
<div class="mb20 btn" @click="submitFn">
登录
</div>
...
<script>
...
methods:{
...
// 点击登录按钮
submitFn() {
// 以msg文字内容来判断是否有进行拼图滑块验证
if (this.msg == "再试一次" || this.msg == "向右滑动") {
alert("请滑动拼图");
return
}
alert("拼图滑块验证通过,可以执行登录了")
},
}
</script>
三、点击获取验证码按钮的逻辑
3.1、逻辑分析
可以正常获取验证码的前提是:手机号格式正确
所以,点击获取验证码的逻辑如下:
1、如果校验手机号格式不正确,则return
2、滑块拼图验证不通过,则return
3、验证成功后,发起请求,获取验证码成功,则进行倒计时
3.2、点击获取验证码判断手机号格式
<div class="btn checkcode-btn" @click="getCode">获取验证码</div>
...
<script>
getCode(){
// 1、验证手机号是否正确
if(!/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/.test(this.phoneNum)){
alert("请输入正确的手机号");
return
}
alert("手机号格式正确");
// 2、进行滑块验证
// 3、验证成功后,发起请求,获取验证码成功,则进行倒计时,并展示秒数
},
</script>
3.3、抽取工具函数(优化)
事实上,验证手机号本身是一个工具函数,和本身逻辑有关但是具体代码不需要在组件中体现。真实项目场景往往是放在工具函数的文件中。
src目录下新建utils文件夹,在里面新建index.js文件:
export const validateTelephoneNumber = value =>{
let reg = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/
return reg.test(value)
}
在LoginBar.vue组件中:
import {validateTelephoneNumber} from "@/utils"
...
...
getCode(){
// 1、验证手机号是否正确
if(!validateTelephoneNumber(this.phoneNum)){
alert("请输入正确的手机号");
return
}
// 2、进行滑块验证
...
// 3、验证成功后,发起请求,获取验证码成功,则进行倒计时,并展示秒数
...
},
3.4、倒计时及其展示
连续点击倒计时会有bug,数字越跳越快,主要是重复开启倒计时造成的。
其实我们只需要把事件给到 "获取验证码" 所在的span,就可以解决
<div class="btn checkcode-btn">
<span v-show="!isShowCount" @click="getCode">获取验证码</span>
<span v-show="isShowCount">{{count}} s</span>
</div>
<script>
data () {
return {
// 是否展示表单的布尔值
isShowForm:true,
// 拼图滑块的文字
msg: "向右滑动",
// 用户手机号
phoneNum:"",
// 最大的计时时间
countMax:60,
// 倒计时时间,每秒变化的那个数字
count:0,
// 是否展示秒数所在盒子
isShowCount:false
}
},
...
...
countdown(){
// 计时的方法
// 倒计时,实际上就是每隔1秒,count减去1
let timer = null;
this.count = this.countMax;
timer = setInterval(()=>{
this.count--
if(this.count===0){
// 清除定时器
clearInterval(timer);
}
},1000);
}
</script>
3.5、发起获取验证码请求
接口: /sendSMS
api.js文件中
// 发送短信验证码请求
export const SendSMSAPI = params => request.post("/sendSMS",params);
在LoginBar.vue组件中:
// 短信验证码请求
import {SendSMSAPI} from "@/request/api"
async getCode(){
// 1、验证手机号是否正确
// 2、进行滑块验证
// alert("验证通过了,可以获取验证码");
let res=await SendSMSAPI({
// phone是接口的参数
phone:this.phoneNum.trim()
});
console.log(res);//此时需要用到qs模块
if(res.code==0){
this.countdown();//进行倒计时,并展示秒数
this.isShowCount=true;
}else{
alert(res.message)
}
// 3、验证成功后,发起请求,获取验证码成功,则进行倒计时,并展示秒数
},
此时会报错,需要用到qs模块
拓展:qs模块:console.log(qs.stringify({name:'Vue'}));//"name=Vue"
安装qs模块: npm i qs
api.js中:
import qs from "qs"
// 发送短信验证码请求
export const SendSMSAPI = params => request.post("/sendSMS",qs.stringify(params));
注意:后端对每一个手机号做了限制,达到最大可发送短信条数,请隔数个小时后再尝试: