尚品汇电商项目总结
项目中提到的知识点需要熟悉,非常大的概率会被问到为什么使用vuex的模块开发根据不同的业务,将该业务state、mutations、action、getters 的封装在一个js文件中,让多种数据分类更加明确,代码更好维护。 --> 将store大仓库分为一个一个业务(功能) 的小仓库vuex模块化开发的使用1.在store大仓库的modules配置下挂载小仓库,小仓库都开启了命名空间2.获取数据
文章目录
尚品汇项目总结
- 利用JWT实现了登陆注册功能,登录成功后返回两个标识
token
和refresh token
。token
是登录凭证,有效使其非常短。refresh token
是刷新凭证时间比token
长,当token
过期时请求新的token。保存在localstorage和vuex里面。退出登录的时候清除。 - 封装了axios,在请求拦截器里向请求头添加token,响应拦截器里做token过期的处理
- 利用全局路由守卫
beforeEach
做登录鉴权操作,利用token判断该路径是否可以跳转。
页面展示主要实现的是性能优化
- 一级菜单绑定鼠标事件监听,当鼠标频繁进入时,事件回调被频繁执行。当用户操作很快时,移入的一级分类都应该触发鼠标进入事件,但是经过测试,只有部分的一级分类被触发了。原因是用户行为过快,导致浏览器没有反应过来。如果当前回调中有大量业务,有可能出现浏览器卡顿现象。所以采用节流防抖、事件委托进行性能优化
- 图片展示页面利用图片懒加载技术,当图片快进入视口时才请求图片资源。
做了什么事情
登录与注册 JWT(Json Web Token)
如何保证安全?加密+短时间刷新token
在传输中,不允许明文传输用户隐私数据
在本地,不允许明文保存用于隐私数据
在服务器,不允许明文保存用户隐私数据
- 服务器-注册接口:接收客户端传来的账号和密码,将其保存在数据库中;
- 服务器-登录接口:接收客户端传来的账号和密码,与数据库比对,完全命中则登录成功,否则登录失败;
- 登录成功后,生成或更新 token 和过期时间,保存在数据库, token 返回给客户端;
- 服务器定期清除 token;
- 客户端-注册模块:向服务器注册接口发送账号和密码;
- 客户端-登录模块:向服务器登录接口发送账号和密码;
- 登录成功后,保存 token 到本地;
- 退出登录后,清除 token;
如何防止token泄露?
类似数字签名机制,利用时间戳和token加密算法 = token签名?
token在前台表示已登陆,在后台表示有权限访问接口。
如果有更需要保护的可以将加入其他验证,如手机号、邮箱等。
token主动刷新
如果token过期,主动刷新token,利用与token相关联的refresh token,refresh token作用是获取新的token,过期时间比token的时间长。
将token过期的处理放在响应拦截器中,当返回的响应码为401时,说明token过期了,需要利用refresh token重新获取新的token。重新获取token之后,重发请求。
- 新token请求成功
- 更新本地token
- 再发一次请求A
- 新token请求失败
- 清空vuex中的token
- 携带请求地址,跳转到登陆页
Refresh Token 及过期时间是存储在服务器的数据库中,只有在申请新的 Token 时才会验证
实现介绍
登录时后端为了区分用户,会返回给前端token,token是用户的唯一标识符。
- 浏览器登录后,服务器利用用户信息,经过加密后生成Token字符串(Token中包含了用户信息),将生成的Token字符串返回给客户端
- 客户端通过代码将Token存储到LocalStorage或SessionStorage
vuex存储数据不是持久化,刷新之后数据会消失
- 客户端再次发送请求时,通过请求头的
Authorization
字段(手动设置),将Token发送给服务器
每一次发请求时,将token放在请求头中一起发送,在请求拦截器中实现。
//请求拦截器
requestAxios.interceptors.request.use((config)=>{
if(store.state.detail.uuid_token) {
config.headers.userTempId = store.state.detail.uuid_token;
}
let token = localStorage.getItem('TOKEN');
if(token){
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
})
对axios进行二次封装
封装的目的
- 请求拦截器:设置发送请求前的统一操作
- 响应拦截器:请求响应后进行统一操作
- 对不同的需求(请求前缀)创建不同的axios请求实例
- api请求(从后台获取数据),’/api‘开头的前缀
- mock请求(mockjs模拟的数据),
/mock
开头的前缀
进行了哪些封装?
请求拦截器
- 请求头中添加token给服务器
- 设置请求超时时间
响应拦截器 - 请求成功直接获取
res.data
,请求失败终止promise链 - 无权限处理,主动刷新token
使用mockjs模拟数据
mockjs 生成随机数据,当前端使用mock模拟的数据接口时,mockjs进行数据返回,并拦截ajax请求不发送给后台。
封装一个mock请求的axios
const mockRequests = axios.create({
baseURL:"/mock",
timeout:5000, //请求超时的时间5s
})
//请求拦截器
mockRequests.interceptors.request.use((config)=>{
//config:配置对象,对象里面有一个属性很重要,header请求头
nprogress.start();//进度条开始
return config;
})
//响应拦截器
//参数1成功的回调,参数2失败的回调
mockRequests.interceptors.response.use((res)=>{
nprogress.done();//进度条结束
return res.data;//返回服务器返回的数据
},(error)=>{
return Promise.reject(new Error('fail')) //终止promise链
})
export default mockRequests;
//采用mock发送请求
import mockRequests from "./request";
//获取Home首页轮播图banner的结构
export const getBannerList = () => mockRequests.get('/banner');
分页器 封装通用组件
分页器需要哪些数据?
1.当前是第几页 pageNo
2.每一页需要展示多少数据 pagesize
3.分页器一共有多少条数据 total
4.分页器显示的连续页码个数:5 | 7
对于分页器,很重要的点是计算出连续显示页面号起始数字和结束数字。 当前页在连续页的正中间
- 计算总共多少页:
Math.ceil(total/pagesize)
- 计算出连续的页码的起始数字与结束数字
computed: {
//总共多少页
totalPage() {
return Math.ceil(this.total / this.pageSize);
},
//计算出连续的页码的起始数字与结束数字[连续页码的数字:至少是5]
startNumAndEndNum() {
const { continues, pageNo, totalPage } = this;
//先定义两个变量存储起始数字与结束数字
let start = 0,
end = 0;
//不正常现象【总页数没有连续页码多】
if (continues > totalPage) {
start = 1;
end = totalPage;
} else {
//正常现象【连续页码5,但是你的总页数一定是大于5的】
start = pageNo - parseInt(continues / 2);
end = pageNo + parseInt(continues / 2);
//把出现不正常的现象【start数字出现0|负数】纠正
if (start < 1) {
start = 1;
end = continues;
}
//把出现不正常的现象[end数字大于总页码]纠正
if (end > totalPage) {
end = totalPage;
start = totalPage - continues + 1;
}
}
return { start, end };
},
}
性能优化
事件委托
将多个子元素的同类事件监听委托给(绑定在)共同的一个父组件上。
好处
①减少内存占用(事件监听的回调变少)
②动态添加的内部元素也能响应
节流和防抖 --遇见的问题也可以回答:卡顿现象
问题描述
给一级菜单绑定了鼠标的事件监听,当鼠标频繁进入时,事件回调被频繁执行。当用户操作很快时,移入的一级分类都应该触发鼠标进入事件,但是经过测试,只有部分的一级分类被触发了。原因是用户行为过快,导致浏览器没有反应过来。如果当前回调中有大量业务,有可能出现浏览器卡顿现象。
问题解决
- 节流(throttle):控制事件执行的时间间隔,在函数需要频繁触发时: 函数执行一次后,只有大于设定的执行周期后才会执行第二次
适合多次事件按时间做平均分配触发:窗口调整(resize)+ 页面滚动(scroll)等 - 防抖(debounce):在函数需要频繁触发时: 在规定时间内,只让最后一次生效,前面的不生效。
适合多次事件一次响应的情况:输入框实时搜索联想(keyup/input)
节流函数实现
节流(throttle)函数
控制的是给事件绑定的回调函数执行的频率,那么该函数的返回值应该是一个函数。
参数有两个1.获取到的回调函数 2. 设置的时间间隔
注意点
1.返回函数使用了闭包,闭包会永远在内存中保存所以这个pre都是记录的上一次的结果
2.修改this的目的是让函数的指向绑定事件的DOM
//使用形式,绑定时候throttle函数就会执行,所以this是window
window.addEventListener('scroll',throttle(()=>{},500))
//自定义
function throttle(callback,wait){
let pre=0;
//console.log(this);window
//节流函数/真正的事件回调函数
return function(...args){
const now = Date.now();
if(now-pre>wait){
//callback()是window调用的,所以callback函数里的this是window,这里要修改指向事件源,
//console.log('this2',this); //DOM
callback.apply(this,args);
pre = now;
}
}
}
防抖函数实现
防抖(debounce)函数
控制的是给事件绑定的回调函数执行的频率,那么该函数的返回值应该是一个函数。
参数有两个1.获取到的回调函数 2. 设置的规定时间
思路
1.返回一个函数,在函数中设置定时器,在定时器中执行回调函数,注意this指向的改变
2.当频繁点击的时候,如果此时已经开启定时器了说明之前触发了回调,我们需要删除定时器
3. 注意定时器的timeId不能在返回函数中定义,如果在返回函数中定义,那么每次触发回调的时候,都会重新定义。而我们的需求是对于当前触发的回调,timeId需要记录之前的结果,通过timeId来判断之前是不是已经开启了定时器。所以这里需要利用闭包实现。
//使用形式:绑定时debounce立即执行
window.addEventListener('scroll',debounce(()=>{},500));
//防抖函数
function debounce(callback, wait){
let timeId=null;
return funtion(...args){
if(timeId){//之前已经有一个定时器了,这里再一次触发事件,重新开始即使
clearTimeout(timer);
}
timeId = setTimeout(()=>{
callback.apply(this,args);
//执行成功之后,重置timeId,所以这里可以起作用
timeId = null;
},wait)
}
}
导航条 只发送一次请求
当组件之间进行切换时,会销毁旧组件,创建新组件。所以组件中的子组件导航也会重新创建实例、重新挂载,重新发送数据请求。
如何优化
对于导航组件来说,一般都是不变的,所以我们希望只发送一次数据请求。所以可以把数据请求放在根组件,根组件只会实例化一次。
图片懒加载和路由懒加载
图片懒加载:https://blog.csdn.net/qq_41370833/article/details/125284975 重点
路由懒加载:https://blog.csdn.net/qq_41370833/article/details/125299151
遇见的问题
1.编程式路由跳转的NavigationDuplicated警告报错 --重写push和replace方法
- 声明式导航router-link(需要有to属性),可以进行路由的跳转
- 编程式导航利用组件实例的
$router.push/replace
,可以进行路由跳转
问题描述
编程式路由重复点击(参数不变),多次执行会抛出NavigationDuplicated警告报错
问题分析,为什么会报错?
Vue router3.1之后,$router.push()
返回Promise,返回的promise没有设置失败的回调,没有对错误进行处理
解决办法
1.对每个router.push()
进行错误捕获
router.push('xxxx').catch(err => {err})
push方法还可以传入成功和失败的回调
this.$router.push({
name:'search',//路由记得命名
params:{keyword:this.keyword},
query:{keyword:this.keyword.toUpperCase()}
},()=>{},(err)=>{if(如果是NavigationDuplicated错误)console.log(err)})
2.重写push()方法
①先保存VueRouter原型上的push方法
②重写push|repalce
import VueRouter from 'vue-router' //引入插件
Vue.use(VueRouter) //使用插件
let origiPush = VueRouter.prototype.push
VueRouter.prototype.push = function(location,onResolved=()=>{},onRejected=(err )=>err){
return origiPush.call(this,location,onResolved,onRejected)
}
2.利用swiper插件实现轮播图时,不生效 --引出$nextTick原理的知识点
原因
在new Swiper实例之前,页面中的结构必须存在
刚开始将数据请求放在mounted里,但是ajax请求是异步的,数据是动态获取的,new Swiper时可能数据还没有获取到,或者说页面还没有根据数据重新渲染,结构还不完整。
mounted() {
//派发action,通过vuex发起ajax请求
this.$store.dispatch('home/reqBannerList');
new Swiper(document.querySelector('.swiper-container'),{
loop:true,
pagination:{
el:".swiper-pagination"
},
navigator:{
nextEl:".swiper-button-next",
prevEl:".swiper-button-prev"
}
})
}
解决办法
watch: 数据监听,监听已有数据变化。 此时只能保证数据已经获取到了,不能保证v-for
dom渲染完毕了
$nextTick:将回调延迟到下次DOM更新之后执行
本质:将回调添加到任务队列中延迟执行
更新DOM的回调
和vm.$nextTick
注册的回调,都是添加到微队列中。所以DOM会先更新完毕,然后再执行$nextTick
的回调
$nextTick原理:https://blog.csdn.net/qq_41370833/article/details/124830714
滚动条保持原有位置
问题描述
当从页面跳转到新路由时,滚动条保持原有位置
原因
路由切换时没有重新刷新页面
解决办法
使用前端路由,当切换到新路由时,想要页面滚动到顶部或者保持原先的滚动位置,vue-router可以实现,只支持在history.pushState
的浏览器使用。 —引出H5接口的新方法pushState
和replaceState
,或者hash模式和history模式的区别
//配置路由
export default new VueRouter({
routes,
scrollBehavior (to, from, savedPosition) {
return {x:0,y:0} //每次路由切换时的滚动条位置
}
})
更多推荐
所有评论(0)