通过前端项目Mall-project (https://github.com/Ray2310/MallProject)使得对于vue技术的实现有了大致的了解和使用。 这里我将具体到一个模块的完成, 从而实现对于vue技术在登录模块下的各个方面的细致讲解。
首先,我们按照vue的思想, 通过组件的形式来完成对于项目的code。 因此按照项目的UI图 以及 登录模块的接口文档, 我们将项目划分为以下内容来进行将解
项目UI图
页面布局之顶部导航
顶部导航栏, 我们可以通过使用vant中的组件来实现,这样大大减少了code的工作量
首先我们通过使用vant组件库的按需导入, 从而实现压缩项目体积, 提升了项目的加载速度和性能, 同时也可以提升网络请求。
所以这里采用按需导入而不是全部导入。
组件导入实现步骤
- 创建
utils/vant-ui.js
作为专门封装vant组件的js模块, 我们只需要再main.js
中导入即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
import Vue from 'vue'
import { Tabbar, TabbarItem , NavBar, Toast, Search, Swipe, SwipeItem, Grid, GridItem} from 'vant'
Vue.use(Tabbar) Vue.use(TabbarItem) Vue.use(NavBar) Vue.use(Toast) Vue.use(GridItem) Vue.use(Search) Vue.use(Swipe) Vue.use(SwipeItem) Vue.use(Grid)
|
1 2 3
|
import '@/utils/vant-ui'
|
- 在页面布局模块
views/login/index.vue
使用导入的需要的组件NavBar
1 2 3 4 5 6 7 8 9 10 11 12 13
| <div class="login"> <!-- 上方使用 NavBar 导航栏 --> <van-nav-bar title="会员中心" left-text="返回" right-text="按钮" left-arrow @click-left="onClickLeft" @click-right="onClickRight" />
<van-nav-bar title="会员中心" left-text="" right-text="" left-arrow @click-left="$router.go(-1)"/>
|
注意: 这里有个返回上一页的箭头, 我们使用路由的方法来实现** @click-left="$router.go(-1)"**
页面布局之主体部分
通过上面的组件导入步骤介绍, 我们也大致知道了组件如何导入及其使用, 接下来的基本所有内容我们都是通过组件的形式实现的, 有的是使用vant组件库, 有的是我们自己封装实现。 下面就是页面布局的主体部分。就是通过我们自己封装的组件。
封装组件实现主题部分
其实这个模块也是可以复用的, 下次也就是改改里面的内容即可, 所以这也就是人们常说code就是ctrl+C/V
了, 因为coder追求的就是极致的便捷、快速。但是这对于初学者我认为还是不够友好的,因为还没有明白原理便开始CV, 那么也只是咀嚼别人吃过的, 没有自己的思想味道。
回归正题….
主题部分也是在views/login/index.vue
中实现的, 只是用了不同的盒子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| <template> <div class="login"> <van-nav-bar title="会员登录" left-arrow @click-left="$router.go(-1)" /> <div class="container"> <div class="title"> <h3>手机号登录</h3> <p>未注册的手机号登录后将自动注册</p> </div>
<div class="form"> <div class="form-item"> <input class="inp" maxlength="11" placeholder="请输入手机号码" type="text"> </div> <div class="form-item"> <input class="inp" maxlength="5" placeholder="请输入图形验证码" type="text"> <img src="@/assets/code.png" alt=""> </div> <div class="form-item"> <input class="inp" placeholder="请输入短信验证码" type="text"> <button>获取验证码</button> </div> </div>
<div class="login-btn">登录</div> </div> </div> </template>
<script> export default { name: 'LoginPage' } </script>
<style lang="less" scoped> .container { padding: 49px 29px;
.title { margin-bottom: 20px; h3 { font-size: 26px; font-weight: normal; } p { line-height: 40px; font-size: 14px; color: #b8b8b8; } }
.form-item { border-bottom: 1px solid #f3f1f2; padding: 8px; margin-bottom: 14px; display: flex; align-items: center; .inp { display: block; border: none; outline: none; height: 32px; font-size: 14px; flex: 1; } img { width: 94px; height: 31px; } button { height: 31px; border: none; font-size: 13px; color: #cea26a; background-color: transparent; padding-right: 9px; } }
.login-btn { width: 100%; height: 42px; margin-top: 39px; background: linear-gradient(90deg,#ecb53c,#ff9211); color: #fff; border-radius: 39px; box-shadow: 0 10px 20px 0 rgba(0,0,0,.1); letter-spacing: 2px; display: flex; justify-content: center; align-items: center; } } </style>
|
功能实现之输入内容提示
用户输入了手机号, 但是输入了13位或在1位,那么我们就需要给个提示, 我们要求的手机号是11位。或者说输入的内容经过我们后端的校验发现是错的,那么我们前端也需要进行提示
校验手机号和图形验证码
1 2 3 4 5 6 7 8 9 10 11 12 13
| validFn(){ if (!/^1[3-9]\d{9}$/.test(this.mobile)) { this.$toast('请输入正确的手机号') return false } if (!/^\w{4}$/.test(this.picCode)) { this.$toast('请输入正确的图形验证码') return false } return true },
|
Toast提示
之前我们可能使用的alert
, 但是学习了vue之后,组件化开发的思想深入脑海, 所以翻找组件库vant ,我们发现Toast
这个组件就可以进行提示, 所以按照上面的组件导入思路,我们就可以实现下面这样的效果。
我们进行需要的时候可以直接使用Toast('提示的内容')
来实现,而他的本质其实是通过
将方法, 注册挂载到了Vue原型上this.$toast('提示内容')
功能实现之图形验证码
在获取图形验证码之前,我们需要对请求进行封装, 因为随着项目开发的深入, 代码随着堆积成山, 如果不进行封装维护, 那么就会形成别人口中的“始(shi)山代码” ,所以为了我们项目的可维护性,我们需要对请求进行封装
封装所有的请求及其login模块的请求
在utils/request.js
模块 ,我们将所有的请求都封装到这里, 这样就便于项目的维护
这些内容都可以通过参考axios官网来实现。
请求响应的封装
响应拦截器是咱们拿到数据的 第一个 “数据流转站”,可以在里面统一处理错误,只要不是 200 默认给提示,抛出错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| import { Toast } from "vant" import axios from "axios"
const instance = axios.create({ baseURL: 'http://cba.itlike.com/public/index.php?s=/api/', timeout: 5000, });
instance.interceptors.request.use(function (config) { Toast.loading({ message: '加载中...', forbidClick: true, loadingType: 'spinner', duration: 0 }) return config;
}, function (error) { return Promise.reject(error); });
instance.interceptors.response.use(function (response) { const res = response.data if (res.status !== 200) { Toast(res.message) return Promise.reject(res.message) }else{ Toast.clear() } return res }, function (error) { return Promise.reject(error) })
export default instance
|
通过上面的工具类封装,我们所有的请求口都会先经过 请求和响应拦截器, 然后再放行,同时, 我们配置了baseURL
基地址,这样以后项目中使用直接使用对应的url就行了。
接下来就是我们login模块的请求封装了, 如果前面封装的是所有的,但是每个模块的请求也需要进行封装才能方便使用。 所以我们将所有的请求都封装到了api
模块中, 然后在api/login.js
中再封装我们的登录模块的请求。
1 2 3 4 5 6 7 8
|
import request from '@/utils/request' export const getPicCode = ()=> { return request.get('/captcha/image') }
|
实现图形验证码回显
注意,我们获取图形验证码需要一进入登录页面就需要显示出来, 所以在views/login/index.vue
中需要在created(){}
方法中实现, 同时, 如果用户看不清图形验证码想要点击图形验证码换一张的时候,我们也需要提供对应的方法来进行实现
1 2 3 4 5 6 7
| <!-- 点击重新切换验证码 --> <img :src="picUrl" @click="getPicCode()" alt="">
async created() { // 通过调用方法来实现图片验证码的显示 this.getPicCode() },
|
通过上述得到的验证码应该是一个 res.data里面的base64
就是我们图片的地址, 我们需要将其拿出来渲染到页面上, 所以就需要参数来接收请求得到的内容。 所以就需要定义变量
1 2 3 4 5 6 7 8 9 10
| export default{ data() { return { piccode: '', picKey: '', picUrl: '', } } }
|
同时在页面中渲染出来
1 2 3 4 5 6 7 8
| async getPicCode(){ const res = await getPicCode() this.picUrl = res.data.base64 this.picKey = res.data.key },
|
1 2 3 4 5
| <div class="form-item"> <input class="inp" maxlength="5" v-model="picCode" placeholder="请输入图形验证码" type="text"> <img :src="picUrl" @click="getPicCode()" alt=""> </div>
|
通过上述一系列的code, 我们就实现了获取图形验证码, 并且回显到页面上。 既然得到的验证码, 那么接下就可以根据用户输入的手机号发送短信了。
功能实现之短信验证码
在实现短信验证码之前, 我们先联想一下, 如果用户一直点击获取验证码, 但是就是不登录,那么我们服务器虽然不会受影响, 但是一条短信一分钱, 如果被不法分子攻击网站,获取短信验证码没有限制, 那么不到一小时,你可能就被攻击到欠XX云100000元了, 所以有些限制是必须有的,我们通过短信验证码的倒计时可以缓冲, 然后后台再对敏感的手机号做限流处理,这样对于网站的防护就上了一个档次,避免了大部分的攻击和恶意注册。
短信验证码的倒计时提醒
实现效果相信大家都见过,这里我们就直接上实现步骤了。
实现思路就是通过定时器来实现, 最后到时间就删除定时器。
- 首先, 设置三个属性作为设置定时器的属性
1 2 3 4 5 6 7 8
| data() { return { totalSecond: 60, second: 60, timer: null } },
|
- 在我们使用的地方通过点击触发定时器 并且设置定时器的显示文字(离开和显示倒计时)
1 2 3 4 5 6
| <div class="form-item"> <input class="inp" placeholder="请输入短信验证码" type="text"> <button @click="getCode"> {{ second === totalSecond ? '获取验证码' : second + `秒后重新发送`}} </button> </div>
|
- 定时器的js逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| async getCode () { if(!this.validFn()){ return } if (!this.timer && this.second === this.totalSecond) { const res = await getMsCode(this.picCode, this.picKey, this.mobile) if(res.status != 200 ) { this.$toast('图形验证码错误') return } this.timer = setInterval(() => { this.second--
if (this.second < 1) { clearInterval(this.timer) this.timer = null this.second = this.totalSecond } }, 1000)
this.$toast('发送成功,请注意查收') } },
|
- 离开页面。 消除定时器
1 2 3 4
| destroyed () { clearInterval(this.timer) }
|
校验信息
在发送短信验证码之前我们需要做校验手机号和图形延展面的操作. 这个再之前已经提过了, 这里因为逻辑需要我们再来一边
1 2 3 4 5 6 7 8 9 10 11 12 13
| validFn(){ if (!/^1[3-9]\d{9}$/.test(this.mobile)) { this.$toast('请输入正确的手机号') return false } if (!/^\w{4}$/.test(this.picCode)) { this.$toast('请输入正确的图形验证码') return false } return true },
|
点击发送验证码调用的方法getCode()
1 2 3 4 5 6 7 8 9 10 11 12
|
async getCode () { if(!this.validFn()){ return } if (!this.timer && this.second === this.totalSecond) { } },
|
获取短信验证码逻辑
接口信息
js逻辑
在api/login.js
模块, 实现获取短信验证码
1 2 3 4 5 6 7 8 9 10 11 12
| export const getMsCode = (captchaCode,captchaKey,mobile) =>{ return request.post('/captcha/sendSmsCaptcha',{ form:{ captchaCode, captchaKey, mobile } }) }
|
因为根据请求接口, 调用请求的时候需要传入参数, 这样才能够发送验证码。 所以需要在login/index.vue
中定义属性来接收用户输入的数据
1 2 3 4 5 6 7 8 9
| data() { return { piccode: '', picKey: '', mobile: '',
} },
|
实现获取验证码逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
async getCode () { if(!this.validFn()){ return }
if (!this.timer && this.second === this.totalSecond) { const res = await getMsCode(this.picCode, this.picKey, this.mobile) if(res.status != 200 ) { this.$toast('图形验证码错误') return } console.log("短信验证码", res) this.timer = setInterval(() => { this.second--
if (this.second < 1) { clearInterval(this.timer) this.timer = null this.second = this.totalSecond } }, 1000)
this.$toast('发送成功,请注意查收') } },
|
功能实现之封装接口实现登录
接口信息
实现思路
api/login.js
提供登录 Api 函数
1 2 3 4 5 6 7 8 9 10 11
| export const loginClick = ( isParty,mobile,partyData,smsCode) => { return request.post('/passport/login',{ form: { isParty, mobile, partyData, smsCode } }) }
|
login/index.vue
中通过点击事件调用请求
注意, 需要绑定 短信验证码。 并且还需要传参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| data() { return { piccode: '', picKey: '', picUrl: '',
totalSecond: 60, second: 60, timer: null,
mobile: '', picCode: '' ,
isParty: false, partyData:{}, smsCode: '', } },
async loginFn() { if (!this.validFn()) { return } if (!/^\d{6}$/.test(this.smsCode)) { this.$toast('请输入正确的手机验证码') return } const res = await loginClick(this.isParty, this.mobile, this.partyData, this.smsCode) console.log(res) console.log("登录成功") this.$router.push('/') this.$toast('登录成功') }
|
vuex持久化存储登录凭证
对于上述我们实现的登录模块,一旦我们刷新浏览器, 那么登录的信息瞬间就消失了, 用户就得重新登录, 所以我们需要持久化存储登录凭证, 同时登录凭证还需要作为公共信息, 因为在其他模块 比如支付或者购物车模块, 都是需要用户输入登录信息才能够执行的。所以就需要从全局获取登录凭证 ,有了登录凭证才能够登录。
下面就是我们使用vuex来实现登录凭证的存储
vuex管理登录权证信息存储
- token 存入 vuex 的好处,易获取,响应式
- vuex 需要分模块 => user 模块
- 创建
store/modules/user.js
模块
- 创建模板数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| export default { namespaced: true, state() { return { userInfo: { token: '', userId: '' } } }, getters: { }, mutations: { setUserInfo(state, obj){ state.userInfo = obj } }, actions: { },
}
|
- 在
store/index.js
中挂载user模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import Vue from 'vue' import Vuex from 'vuex' import user from './modules/user' Vue.use(Vuex)
export default new Vuex.Store({
state: { }, getters: { }, mutations: { }, actions: { }, modules: { user } })
|
- 页面中进行调用
在login/index.vue
中进行调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { mapMutations } from 'vuex'
methods: { ...mapMutations('user', ['setUserInfo']),
loginFn(){ const res = await loginClick(this.isParty, this.mobile, this.partyData, this.smsCode) console.log(res) this.setUserInfo(res.data) } }
|
调用的注意事项:
vuex的持久化处理
目标:封装 storage 存储模块,利用本地存储,进行 vuex 持久化处理
问题1:vuex 刷新会丢失,怎么办?
1 2
| localStorage.setItem('hm_shopping_info', JSON.stringify(xxx))
|
在utils/storage.js
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const INFO_KEY = 'hm_shopping_info'
export const getInfo = () => { const result = localStorage.getItem(INFO_KEY) return result ? JSON.parse(result) : { token: '', userId: '' } }
export const setInfo = (info) => { localStorage.setItem(INFO_KEY, JSON.stringify(info)) }
export const removeInfo = () => { localStorage.removeItem(INFO_KEY) }
|
修改之前的store/modules/user.js
中的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import { getInfo, setInfo } from "@/utils/storage" export default { namespaced: true, state() { return { userInfo: getInfo() } }, getters: { }, mutations: { setUserInfo(state, obj){ state.userInfo = obj setInfo(obj) }
}, actions: { },
}
|
路由导航守卫
对于有些模块需要登录凭证, 但是有些模块又不需要, 因为我们是实现的商城项目 ,所以登录凭证只有在用户进入购物车或者个人信息模块的时候使用。 其他模块直接放行即可。
所以这里就引入了路由导航守卫, 用来实现请求的过滤,对于需要登录才能访问的需要跳转到登录模块
官网地址: 全局前置守卫
**路由导航守卫 **
- 所有的路由一旦被匹配到,都会先经过全局前置守卫
- 只有全局前置守卫放行,才会真正解析渲染组件,才能看到页面内容
1 2 3 4 5 6 7 8
| router.beforeEach((to, from, next) => { })
|
官网内容
因此, 我们需要对于那些需要访问权限的页面增加守卫前置
在router/index.js
中就可以定义我们路由的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| const router = new VueRouter({ routes: [ ] })
const authUrl = ['/pay','/myorder']
router.beforeEach((to , from, next) => {
const token = store.getters.getToken if(!authUrl.includes(to.path)){ next() return } if(token){ next() }else{ next('/login') } })
export default router
|
在全局的store存放数据模块的store/index.js
中配置获取token, 这样上面的全局导航守卫中想要获取token就可以直接通过getters获取, 而不是通过原生的store.state.user.userInfo.token
。
1 2 3 4 5 6
| getters: { getToken(state){ return state.user.userInfo.token } },
|