文章目录
vue2项目实战尚品汇前台03
用两个符号来标记面试中可以使用到的地方
? 项目中遇见的问题
√ 面试可提点
mockjs 模拟数据 √
mockjs 生成随机数据,当前端使用mock模拟的数据接口时,mockjs进行数据返回,并拦截ajax请求不发送给后台
安装
cnpm install --save mockjs
使用步骤
1.在项目中src文件中创建mock文件夹
2.准备JSON数据 --JSON文件需要格式化,不要留空格
3.把mock数据需要的图片放到public文件夹中,public文件夹在打包的时候,会把相应的资源原封不动打包到dist文件夹中
4.创建mockServer.js通过mock.js插件实现模拟数据
5.在入口文件中引入mockServer.js,该文件至少需要执行一次,才能模拟数据
问题:json文件并没有暴露为什么可以引用?
webpack 默认对外暴露:图片、JSON数据格式
//引入mockjs模块
import Mock from "mockjs";
//引入JSON数据格式
import banner from './banner.json';
import floor from "./banner.json";
//第一个参数请求地址,第二个参数请求数据
Mock.mock("/mock/banner",{code:200,data:banner});
Mock.mock("/mock/floor",{code:200,data:floor});
在入口文件中引入mockServer.js,该文件至少需要执行一次,才能模拟数据。
类似css文件的引入,不需要像外暴露。
import "@/mock/mockServe.js"
mock的使用
先封装一个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');
利用swiper实现轮播图
移动端和pc端都可以使用
安装
cnpm install --save swiper@5
使用看文档
获取动态数据的步骤
1.在使用的组件中,通过dispath触发action对应的函数执行
2.action执行操作后,向mutation中对应的函数使用commit提交数据
3.mutation中函数操作vuex的修改
watch + $nextTick 解决数据异步获取导致后续代码失效问题 ?
在new Swiper实例之前,页面中的结构必须存在
- watch 保证数据已经ok
- $nextTick 保证页面v-for渲染完毕
所以我们可以放在mounted,但是数据是动态获取的,ajax请求是异步的,new Swiper时可能数据还没有获取到,页面还没有根据数据重新渲染,结构还不完整。
这样写会没有动态效果!
mounted() {
//派发action,通过vuex发起aja请求
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渲染完毕了。
watch:{
//监听bannerList数据的变化,异步获取到数据后bannerList会从空变成有数据的数组
bannerList:{
handle(newVal,oldVal){
let swiper = new Swiper(document.querySelector('.swiper-container'),{
loop:true,
pagination:{
el:".swiper-pagination",
clickable:true
},
navigator:{
nextEl:".swiper-button-next",
prevEl:".swiper-button-prev"
}
})
}
}
}
$nextTick
在下次DOM更新循环结束后(bannerList数据获取完v-for更新DOM)执行延迟回调。
在修改数据之后立即使用这个办法,获取更新后的DOM
如果在mounted里直接使用$nextTick会失效,因为此时数据可能还没有修改!
所以 $nextTick回调执行的时机: 在修改数据之后立即使用时,等界面是更新后的DOM之后调用回调
$nextTick的作用:在下次DOM更新循环结束后,执行延迟回调
总结
1.数据已经修改完毕使用$nextTick指定回调
2.在页面已经更新完毕(v-for结束)执行回调
组件的传值 ★
父子组件加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
getters使用
getters配置项:用于对state中的数据进行加工,类似于计算属性
项目当中getters主要的作用是:简化仓库中的数据(简化数据而生)
//search.js
//计算属性
//项目当中getters主要的作用是:简化仓库中的数据(简化数据而生)
//可以把我们将来在组件当中需要用的数据简化一下【将来组件在获取数据的时候就方便了】
const getters = {
//当前形参state,当前仓库中的state,并非大仓库中的那个state
goodsList(state){
return state.searchList.goodsList||[];
}
,
trademarkList(state){
return state.searchList.trademarkList||[];
},
attrsList(state){
return state.searchList.attrsList||[];
}
};
//使用
computed: {
...mapGetters('search',['goodsList','trademarkList','attrsList'])
},
生命周期的使用
当跳转到搜索页面时,可能是其他路由传了参数,所以在mounted发送请求之前,需要将参数合并。
可以在cread或者 beforeMount生命周期中进行参数合并
//在挂载之前调用一次|可以在发请求之前将带有参数进行修改
beforeMount() {
//在发请求之前,把接口需要传递参数,进行整理(在给服务器发请求之前,把参数整理好,服务器就会返回查询的数据)
Object.assign(this.searchParams, this.$route.query, this.$route.params);
},
mounted() {
this.getData();
},
methods: {
getData(){
this.$store.dispatch('search/reqSearchList',this.searchParams)
},
},
我们这里将请求数据的函数只在mounted上调用,只有组件挂载成功会调用一次,其他时候比如点击分类点击搜索按钮都不会调用。参数整理也只在beforeMount时整理了一次。
因为可以搜索的地方太多了,这里可以选择监视route的改变,当路径改变时先整理参数再调用请求函数。
methods: {
getData(){
//axios会把undefined的参数会丢弃
this.searchParams.category1Id = undefined;
this.searchParams.category2Id = undefined;
this.searchParams.category3Id = undefined;
this.$store.dispatch('search/reqSearchList',this.searchParams);
}
},
watch:{//监听的属性不用写this
$route(newVal,oldVal){
Object.assign(this.searchParams, this.$route.query, this.$route.params);
this.getData();
}
}
axios会把undefined的参数会丢弃,不会传给服务器
分页器 √ 可以回答有没有封装过组件
分页器需要哪些数据?
1.当前是第几页 pageNo
2.每一页需要展示多少数据 pagesize
3.分页器一共有多少条数据 total
Math.ceil(total/pagesize)
页数
4.分页器显示的连续页码个数:5 | 7
对于分页器,很重要的点是计算出连续页面起始数字和结束数字。 当前页在连续页的正中间
- parseInt()和Math.floor()向上取整
- Math.round()四舍五入
- Math.ceil() 向上取整
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 };
},
}
v-for 遍历数字
v-for使用范围Array|Object|number|string|iterable
v-for遍历数字从1开始,所以在实现中间连续区域时,比start小的位置需要隐藏
1.如果start>1
说明第一个1是需要的,如果start=1
说明第第一个1是不需要的,使用start的1
2.开头的…只有当start>2
的时候才出现
3.结尾的地方同理
4.点击页数的时候需要将页数传递给父组件进行数据请求
滚动条保持原有位置 √
问题描述:当从页面跳转到新路由时,滚动条保持原有位置
使用前端路由,当切换到新路由时,想要页面滚动到顶部或者保持原先的滚动位置,vue-router
可以实现,只支持在history.pushState
的浏览器使用。
//配置路由
export default new VueRouter({
routes,
scrollBehavior (to, from, savedPosition) {
return {x:0,y:0} //每次路由切换时的滚动条位置
}
})
详情页
排他操作
售卖属性
[
{
attr:'颜色',
attrValue:['粉色','天蓝色','黑色']
},
{
attr:'版本',
sttrValue:['16','18','22']
}
]
需求:点谁谁高亮,高亮是通过对象中的isCheck属性确认的。
所以可以利用排他思想。
1.点击的是谁
2.数组中一共有哪些元素
遍历数组中的所有元素,将点击元素的isCheck设置为1,其余设置为0
methods: {
//产品售卖属性值切换高亮
changeActive(value,arr){
arr.forEach(item => {
item.isChecked='0';//将所有设置成0
});
value.isChecked='1'; //点击的设置成1
}
},
放大镜
一个盒子可以跟着鼠标去动,那么这个盒子肯定是脱离文档流了,通常设置left和top来改变盒子的位置。
1.获取鼠标的位置
2.mask监听鼠标移入图片事件,根据鼠标的位置设置mask的新位置
handler(event) {
let mask = this.$refs.mask;
let big = this.$refs.big;
let left = event.offsetX - mask.offsetWidth/2;
let top = event.offsetY - mask.offsetHeight/2;
//约束范围
if(left <=0) left = 0;
//两个mask的宽度 = 外层显示图片img的盒子宽度
if(left >=mask.offsetWidth) left = mask.offsetWidth;
if(top<=0)top = 0;
if(top>=mask.offsetHeight) top = mask.offsetHeight;
//修改元素的left|top属性值
mask.style.left = left+'px';
mask.style.top = top +'px';
//mask右移,显示的big大图应该往左移,big的大小是img的两倍,所以移动需要*2
big.style.left = - 2 * left+'px';
big.style.top = -2 * top +'px';
},
购物车
购物数量的输入验证
非数字的字符 * 1 = NaN
来排除非法输入
//表单元素修改产品个数
changeSkuNum(event) {
//用户输入进来的文本 * 1
let value = event.target.value * 1;
//如果用户输入进来的非法,出现NaN或者小于1
if (isNaN(value) || value < 1) {
this.skuNum = 1;
} else {
//正常大于1【大于1整数不能出现小数】
this.skuNum = parseInt(value);
}
}
在组件中获取actions里的函数执行结果
1.点击购物车,需要把购买信息发送个服务器
2.服务器存储成功,进行路由跳转。如果失败,则需要给用户进行提示。
已经派发了actions,向服务发送了请求,如何在组件中获取actions里的函数执行结果?
async 函数返回值为Promise对象,promise对象的结果由async函数执行的返回值决定。
所以我们可以将请求返回的状态码返回给组件
//仓库里的addOrUpdate函数
const actions = {
//....
async addOrUpdate({commit},{skuId,skuNum}){
let result = await getAddOrUpdate(skuId,skuNum);
return result.code
}
};
代码的作用调用detail仓库里的addOrUpdate函数,函数返回值是一个promise.
await相当于then的语法糖 直接得到结果,会阻塞后面的代码
//代码的作用调用detail仓库里的addOrUpdate函数,函数返回值是一个promise
async addShopcar() {
try {
//成功
await this.$store.dispatch("addOrUpdateShopCart", {
skuId: this.$route.params.skuid,
skuNum: this.skuNum,
})
}
游客身份获取购物车数据
生成游客ID,游客ID应该固定,且可以持久存储。
import { v4 as uuidv4 } from 'uuid';
//要生成一个随机字符串,且每次执行不能发生变化,游客身份持久存储
export const getUUID = ()=>{
//先从本地存储获取uuid(看一下本地存储里面是否有)
let uuid_token = localStorage.getItem('UUIDTOKEN');
//如果没有
if(!uuid_token){
//生成游客临时身份
uuid_token = uuidv4();
//本地存储存储一次
localStorage.setItem('UUIDTOKEN',uuid_token);
}
return uuid_token;
}
在将购物车的数据传给后台时,在请求头加上用户的id。
这里是因为已经和后台沟通好了,需要在请求头上加上userTempId字段才能将购物车信息返回。
//请求拦截器
requestAxios.interceptors.request.use((config)=>{
if(store.state.detail.uuid_token) {
//请求头添加字段,该字段已经和后台商量好了
config.headers.userTempId = store.state.detail.uuid_token;
}
//config:配置对象,对象里面有一个属性很重要,header请求头
nprogress.start();//进度条开始
return config;
})
登录与注册
token √
登录时后端为了区分用户,会返回给前端token,token是用户的唯一标识符。
vuex存储数据不是持久化,刷新之后数据会消失,所以需要把token持久化存储在本地localStorage
每一次发请求时,将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.token = token ;
}
return config;
})
路由守卫 √
导航:路由正在发生变化,路由跳转
- 全局守卫 VueRouter的实例方法
- 全局前置守卫 beforeEach()
- 全局后置守卫 afterEach()
- 路由独享守卫 beforeEnter() -路由配置写法
- 组件内守卫 -生命周期的写法(不常用)
- 进入组件时被调用 beforeRouteEnter() 此时不能获取组件实例this
- 离开组件时被调用 beforeRouteLeave()
使用
1.在每次路由跳转时,根据token和用户信息判断用户是否登录,从而判断该路由是否可以跳转。
用户信息是因为页面需要使用,token来判断用户是否登录。如果token失效了,需要重新登录,也就是可以执行退出登录的流程。
2.没有登录时,有些路由不能进入(通过全局路由守卫实现),登录之后应该跳转到想要访问的路由。所以可以先把登录的路由作为query参数,点击登录的时候根据url有无query参数再决定往哪里跳转(在登陆组件中点击登录事件触发的回调中实现)。
支付
将请求api挂载在vue原型对象上 √
如果不想通过vuex发送http请求,可以将请求api挂载在vue原型对象上,在组件中使用时就不需要单独引用api,直接使用$API
即可。
import * as API from ’@/api‘
new Vue({
beforeCreate(){
Vue.prototype.$API = API
}
})
Element-ui 按需引用
Element-ui : vue pc端
import {Button,MessageBox} from ’element-ui‘
//注册全局组件 第一参组件名字、使用时用这个名字 第二个参数组件
Vue.component(Button.name,Button)
//写法2:挂在原型上
Vue.prototype.$msgbox = MessageBox
长轮询:一直发请求
需求:需要知道支付成功或者失败,要一直去后台请求获取当前状态
async open(){
//...生成二维码等信息
if(!this.timer){
this.timer = setInterval(()=>{
//发送请求获取支付状态
//....
//如果支付成功,需要清除定时器,执行后续操作
clearInterval(this.timer);
this.timer=null;
//....后续操作
},1000)
}
}
图片懒加载 √
插件: vue-lazyload
https://blog.csdn.net/qq_41370833/article/details/125284975?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22125284975%22%2C%22source%22%3A%22qq_41370833%22%7D&ctrtid=LkD7s
自定义插件
插件需要暴露一个对象,该对象一定要有install
方法
使用插件
1.先引入
2.Vue.use(插件)
实际上就是调用插件的install()
,还会把Vue构造器传参给install方法
let myPlugins={};
myPlugins.install = function(Vue.options){
//第一个参数是Vue的构造函数,第二个参数是配置参数
/*
可以访问到Vue.prototype
Vue.directive
Vue.component...
*/
//假设配置参数有一个name属性 传过来的配置参数为{name:upper}
Vue.directive(options.name,(ele,b)=>{
/*
在组件中可以使用v-upper 然后就会调用此回调函数
第一个参数为绑定指令的DOM,第二个参数为使用指定时的传参
*/
})
}
export default myPlugins;
路由懒加载 √
作用:解决vue项目首次加载因为加载组件过多加载慢的问题。
路由懒加载:把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件
用户访问组件时,该箭头函数被执行
webpack:import动态导入语法能将该文件单独打包
{
path:'/home',
component:()=> import("@/views/Home")
}
https://blog.csdn.net/qq_41370833/article/details/125299151?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22125299151%22%2C%22source%22%3A%22qq_41370833%22%7D&ctrtid=9odkn