通过前端项目Mall-project (https://github.com/Ray2310/MallProject)使得对于vue技术的实现有了大致的了解和使用。 这里我将具体到一个模块的完成, 从而实现对于vue技术在登录模块下的各个方面的细致讲解。 首先,我们按照vue的思想, 通过组件的形式来完成对于项目的code。 因此按照项目的UI图 以及 登录模块的接口文档, 我们将项目划分为以下内容来进行将解
项目UI图
页面布局之顶部导航
顶部导航栏, 我们可以通过使用vant中的组件来实现,这样大大减少了code的工作量 首先我们通过使用vant组件库的按需导入, 从而实现压缩项目体积, 提升了项目的加载速度和性能, 同时也可以提升网络请求。 所以这里采用按需导入而不是全部导入。
组件导入实现步骤
- 创建
utils/vant-ui.js
作为专门封装vant组件的js模块, 我们只需要再main.js
中导入即可
// utils/vant-ui.js
//把引入组件的步骤抽离到单独的js文件 将需要导入的配置 放在此处。
import Vue from 'vue'
//1. 按需导入组件
import { Tabbar, TabbarItem , NavBar, Toast, Search, Swipe, SwipeItem, Grid, GridItem} from 'vant'
//2. 使用对应的组件
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)
// main.js
// 导入vent中的需要的组件
import '@/utils/vant-ui'
- 在页面布局模块
views/login/index.vue
使用导入的需要的组件NavBar
<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
中实现的, 只是用了不同的盒子。
<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位。或者说输入的内容经过我们后端的校验发现是错的,那么我们前端也需要进行提示
校验手机号和图形验证码
// 校验手机号 和 图形验证码输入是否正确
validFn(){
if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
this.$toast('请输入正确的手机号')
return false
}
// 这个逻辑有问题。 随便输入4位都可以通过
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 默认给提示,抛出错误
// 封装axios请求方法, 封装到request模块
import { Toast } from "vant"
import axios from "axios"
//1. 创建axios实例。 以后通过使用创建出来的axios实例 , 进行自定义配置
// 好处: 不会污染原始的aixos实例
const instance = axios.create({
baseURL: 'http://cba.itlike.com/public/index.php?s=/api/',
timeout: 5000,
});
//2. 自定义配置
//2.1 配置拦截器
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
// 开启loading
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)
})
//3. 导出实例
export default instance
通过上面的工具类封装,我们所有的请求口都会先经过 请求和响应拦截器, 然后再放行,同时, 我们配置了baseURL
基地址,这样以后项目中使用直接使用对应的url就行了。 接下来就是我们login模块的请求封装了, 如果前面封装的是所有的,但是每个模块的请求也需要进行封装才能方便使用。 所以我们将所有的请求都封装到了api
模块中, 然后在api/login.js
中再封装我们的登录模块的请求。
// 登录相关的接口请求
//1. 获取图形验证码
import request from '@/utils/request'
export const getPicCode = ()=> {
return request.get('/captcha/image')
}
//2. 获取短信验证码的请求接口
//3. 点击登录请求接口
实现图形验证码回显
注意,我们获取图形验证码需要一进入登录页面就需要显示出来, 所以在views/login/index.vue
中需要在created(){}
方法中实现, 同时, 如果用户看不清图形验证码想要点击图形验证码换一张的时候,我们也需要提供对应的方法来进行实现
<!-- 点击重新切换验证码 -->
<img :src="picUrl" @click="getPicCode()" alt="">
async created() {
// 通过调用方法来实现图片验证码的显示
this.getPicCode()
},
通过上述得到的验证码应该是一个 res.data里面的base64
就是我们图片的地址, 我们需要将其拿出来渲染到页面上, 所以就需要参数来接收请求得到的内容。 所以就需要定义变量
export default{
data() {
return {
//1. 设置获取图形验证码的参数
piccode: '', // 用户输入的图形验证码
picKey: '', //请求传入图形验证码的唯一标识
picUrl: '', // 存储请求渲染的图片地址
}
}
}
同时在页面中渲染出来
// 获取图形验证码
async getPicCode(){
// 使用自己封装的axios来使用, 这样就不会污染原始的axios请求
// 将所有的请求全部放到api模块去实现
const res = await getPicCode()
this.picUrl = res.data.base64
this.picKey = res.data.key
},
<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元了, 所以有些限制是必须有的,我们通过短信验证码的倒计时可以缓冲, 然后后台再对敏感的手机号做限流处理,这样对于网站的防护就上了一个档次,避免了大部分的攻击和恶意注册。
短信验证码的倒计时提醒
实现效果相信大家都见过,这里我们就直接上实现步骤了。 实现思路就是通过定时器来实现, 最后到时间就删除定时器。
- 首先, 设置三个属性作为设置定时器的属性
data() {
return {
//设置 获取短信验证码 倒计时
totalSecond: 60, // 总秒数
second: 60, // 倒计时的秒数
timer: null // 定时器 id
}
},
- 在我们使用的地方通过点击触发定时器 并且设置定时器的显示文字(离开和显示倒计时)
<div class="form-item">
<input class="inp" placeholder="请输入短信验证码" type="text">
<button @click="getCode">
{{ second === totalSecond ? '获取验证码' : second + `秒后重新发送`}}
</button>
</div>
- 定时器的js逻辑
// 获取短信验证码
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 // 重置定时器id
this.second = this.totalSecond // 归位
}
}, 1000)
// 发送请求,获取验证码
this.$toast('发送成功,请注意查收')
}
},
- 离开页面。 消除定时器
destroyed () {
clearInterval(this.timer)
}
校验信息
在发送短信验证码之前我们需要做校验手机号和图形延展面的操作. 这个再之前已经提过了, 这里因为逻辑需要我们再来一边
// 校验手机号 和 图形验证码输入是否正确
validFn(){
if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
this.$toast('请输入正确的手机号')
return false
}
// 这个逻辑有问题。 随便输入4位都可以通过
if (!/^\w{4}$/.test(this.picCode)) {
this.$toast('请输入正确的图形验证码')
return false
}
return true
},
点击发送验证码调用的方法getCode()
//2. 在发送短信验证码的时候进行调用请求
// 获取短信验证码
async getCode () {
if(!this.validFn()){
return
}
// 发送短信验证码 并且启动倒计时提醒
if (!this.timer && this.second === this.totalSecond) {
// 逻辑...
}
},
获取短信验证码逻辑
接口信息
js逻辑
在api/login.js
模块, 实现获取短信验证码
//1. 在api/login.js中 进行写请求的逻辑
export const getMsCode = (captchaCode,captchaKey,mobile) =>{
// 按照接口文档的要求, 需要传惨
return request.post('/captcha/sendSmsCaptcha',{
form:{
captchaCode,
captchaKey,
mobile
}
})
}
因为根据请求接口, 调用请求的时候需要传入参数, 这样才能够发送验证码。 所以需要在login/index.vue
中定义属性来接收用户输入的数据
data() {
return {
//1. 设置获取图形验证码的参数
piccode: '', // 用户输入的图形验证码
picKey: '', //请求传入图形验证码的唯一标识
mobile: '', // 手机号
}
},
实现获取验证码逻辑
//2. 在发送短信验证码的时候进行调用请求
// 获取短信验证码
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 // 重置定时器id
this.second = this.totalSecond // 归位
}
}, 1000)
// 发送请求,获取验证码
this.$toast('发送成功,请注意查收')
}
},
功能实现之封装接口实现登录
接口信息
实现思路
api/login.js
提供登录 Api 函数
//3. 点击登录请求接口
export const loginClick = ( isParty,mobile,partyData,smsCode) => {
return request.post('/passport/login',{
form: {
isParty,
mobile,
partyData,
smsCode
}
})
}
login/index.vue
中通过点击事件调用请求
注意, 需要绑定 短信验证码。 并且还需要传参
// 需要接受数据的属性
data() {
return {
//1. 设置获取图形验证码的参数
piccode: '', // 用户输入的图形验证码
picKey: '', //请求传入图形验证码的唯一标识
picUrl: '', // 存储请求渲染的图片地址
//2. 设置 获取短信验证码 倒计时
totalSecond: 60, // 总秒数
second: 60, // 倒计时的秒数
timer: null, // 定时器 id
// 设置接收输入框的内容,并且使用v-model进行绑定
mobile: '', // 手机号
picCode: '' ,// 图形验证码
//3. 设置点击登录接口需要的参数
isParty: false, // 是否存在第三方用户信息boolean
// 手机号上面有接收
partyData:{}, // 三方登录信息,默认为:{} 可选
smsCode: '', // 短信验证码, 测试环境验证码为:246810
}
},
// 点击登录的js逻辑
async loginFn() {
if (!this.validFn()) {
return
}
if (!/^\d{6}$/.test(this.smsCode)) {
this.$toast('请输入正确的手机验证码')
return
}
//2. 调用请求信息
const res = await loginClick(this.isParty, this.mobile, this.partyData, this.smsCode)
console.log(res)
console.log("登录成功")
//3. 路由转发
this.$router.push('/')
this.$toast('登录成功')
}
vuex持久化存储登录凭证
对于上述我们实现的登录模块,一旦我们刷新浏览器, 那么登录的信息瞬间就消失了, 用户就得重新登录, 所以我们需要持久化存储登录凭证, 同时登录凭证还需要作为公共信息, 因为在其他模块 比如支付或者购物车模块, 都是需要用户输入登录信息才能够执行的。所以就需要从全局获取登录凭证 ,有了登录凭证才能够登录。 下面就是我们使用vuex来实现登录凭证的存储
vuex管理登录权证信息存储
- token 存入 vuex 的好处,易获取,响应式
- vuex 需要分模块 => user 模块
- 创建
store/modules/user.js
模块 - 创建模板数据
// 用户信息模块的公共数据模块
export default {
namespaced: true,
// 存储数据
state() {
return {
// 暂时提供的数据
userInfo: {
token: '',
userId: ''
}
}
},
// 从state中筛选出符合条件的一些数据(必须要有返回值, 并且第一个参数必须是state)
getters: {
},
// 对象中存放的是修改state的方法(所有的第一个参数必须是state, 然后才是形参)
mutations: {
setUserInfo(state, obj){
state.userInfo = obj
}
},
// actions负责进行异步操作, 一般需要调用mutation中的方法
actions: {
},
}
- 在
store/index.js
中挂载user模块
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. 导入mapMutations
import { mapMutations } from 'vuex'
// 2. 在methods中使用
methods: {
...mapMutations('user', ['setUserInfo']),
//3. 调用请求信息, 并且存储
loginFn(){
const res = await loginClick(this.isParty, this.mobile, this.partyData, this.smsCode)
console.log(res)
//2.1 将登录信息存储到state中
this.setUserInfo(res.data)
}
}
调用的注意事项:
vuex的持久化处理
目标:封装 storage 存储模块,利用本地存储,进行 vuex 持久化处理 问题1:vuex 刷新会丢失,怎么办?
// 将token存入本地
localStorage.setItem('hm_shopping_info', JSON.stringify(xxx))
在utils/storage.js
中
// 持久化存储信息
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
中的内容
// 用户信息模块的公共数据模块
import { getInfo, setInfo } from "@/utils/storage"
export default {
namespaced: true,
// 存储数据
state() {
return {
// 即使获取的内容为空, 他也会帮我们创建一个新的空对象
userInfo: getInfo()
}
},
// 从state中筛选出符合条件的一些数据(必须要有返回值, 并且第一个参数必须是state)
getters: {
},
// 对象中存放的是修改state的方法(所有的第一个参数必须是state, 然后才是形参)
mutations: {
setUserInfo(state, obj){
state.userInfo = obj
setInfo(obj)
}
},
// actions负责进行异步操作, 一般需要调用mutation中的方法
actions: {
},
}
路由导航守卫
对于有些模块需要登录凭证, 但是有些模块又不需要, 因为我们是实现的商城项目 ,所以登录凭证只有在用户进入购物车或者个人信息模块的时候使用。 其他模块直接放行即可。 所以这里就引入了路由导航守卫, 用来实现请求的过滤,对于需要登录才能访问的需要跳转到登录模块
官网地址: 全局前置守卫
**路由导航守卫 **
- 所有的路由一旦被匹配到,都会先经过全局前置守卫
- 只有全局前置守卫放行,才会真正解析渲染组件,才能看到页面内容
router.beforeEach((to, from, next) => {
// 1. to 往哪里去, 到哪去的路由信息对象
// 2. from 从哪里来, 从哪来的路由信息对象
// 3. next() 是否放行
// 如果next()调用,就是放行
// next(路径) 拦截到某个路径页面
})
官网内容
因此, 我们需要对于那些需要访问权限的页面增加守卫前置
在router/index.js
中就可以定义我们路由的内容
const router = new VueRouter({
// 1. 配置路由规则
routes: [
]
})
// 定义数组存放需要用户登录才能访问的页面
const authUrl = ['/pay','/myorder']
// 配置全局导航守卫
router.beforeEach((to , from, next) => {
// 1. to 往哪里去, 到哪去的路由信息对象
// 2. from 从哪里来, 从哪来的路由信息对象
// 3. next() 是否放行
// 如果next()调用,就是放行
// next(路径) 拦截到某个路径页面
// 从全局数据中查看是否存在token = store.state.user.userInfo.token
// 是否是我们用户需要登录才能访问的页面
// 如果不是, 那么就直接 next() 放行
// 通过getters封装我们获取token的请求
const token = store.getters.getToken
if(!authUrl.includes(to.path)){
next()
return
}
// 是需要登录才能访问的页面, 拦截请求, 并且跳转到登录页面
if(token){ // 是否存在token
next()
}else{
next('/login')
}
})
export default router
在全局的store存放数据模块的store/index.js
中配置获取token, 这样上面的全局导航守卫中想要获取token就可以直接通过getters获取, 而不是通过原生的store.state.user.userInfo.token
。
getters: {
// 配置getters 直接获取token
getToken(state){
return state.user.userInfo.token
}
},