vue2项目实战尚品汇前台03 mockjs模拟数据-nextTick-组件间传值-分页器-滚动行为-token-路由守卫-懒加载-路由懒加载-自定义插件

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-fordom渲染完毕了。

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

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue2的el-select懒加载组件是一种用于处理大选项的下拉选择框的解决方案。它可以在用户滚动到下拉列表底部时动态加载更多选项,以提高性能和用户体验。 要实现el-select懒加载组件,你需要使用Vue2的异步组件自定义指令。以下是一个简单的示例代码: ```html <template> <div> <el-select v-model="selectedOption" v-lazy-load="loadOptions"> <el-option v-for="option in options" :key="option.value" :label="option.label" :value="option.value"></el-option> </el-select> </div> </template> <script> export default { data() { return { selectedOption: '', options: [], isLoading: false, page: 1, pageSize: 10 }; }, directives: { 'lazy-load': { bind(el, binding, vnode) { const selectWrapper = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap'); selectWrapper.addEventListener('scroll', function() { const scrollHeight = selectWrapper.scrollHeight; const scrollTop = selectWrapper.scrollTop; const clientHeight = selectWrapper.clientHeight; if (scrollHeight - scrollTop - clientHeight <= 5 && !binding.value.isLoading) { binding.value.loadMore(); } }); } } }, methods: { loadOptions() { // 初始化加载选项 this.loadMore(); }, loadMore() { this.isLoading = true; // 模拟异步加载数据 setTimeout(() => { const newOptions = []; for (let i = 0; i < this.pageSize; i++) { const value = this.page * this.pageSize + i; newOptions.push({ value: value, label: 'Option ' + value }); } this.options = this.options.concat(newOptions); this.page++; this.isLoading = false; }, 1000); } } }; </script> ``` 在上面的示例中,我们使用了自定义指令`v-lazy-load`来监听下拉列表的滚动事件,并在滚动到底部时调用`loadMore`方法加载更多选项。`loadMore`方法模拟了异步加载数据的过程,并将新加载的选项添加到`options`数组中。 你可以根据实际需求修改代码,例如调整每次加载的选项数量、修改异步加载数据的方式等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值