Day2
1、编程式导航路由跳转到当前路由(参数不变), 多次执行会抛出NavigationDuplicated的警告错误?
注意:编程式导航(push|replace)才会有这种情况的异常,声明式导航是没有这种问题,因为声明式导航内部已经解决这种问题。
这种异常,对于程序没有任何影响的。
为什么会出现这种现象:
由于vue-router最新版本3.5.3,引入了promise,当传递参数多次且重复,会抛出异常,因此出现上面现象,
第一种解决方案:是给push函数,传入相应的成功的回调与失败的回调,可以捕获到
第一种解决方案可以暂时解决当前问题,但是以后再用push|replace还是会出现类似现象,因此我们需要从‘根’治病;
this:当前组建的实例
this.$router属性:当前这个属性,属性值VueRouter类的实例,当在入口文件注册路由的时候,给组件实例添加$router|$route属性
push:VueRouter类的一个实例
this.$router.push({name:"search",params:{keyword:this.keyword},query:{k:this.keyword.toUpperCase()}},()=>{},()=>{})
在配置路由的地方
//先把VueRouter原型对象的push,先保存一份
let originPush = VueRouter.prototype.push;
//重写push|replace
VueRouter.prototype.push = function(location,resolve,reject){
//第一个参数:告诉原来的push方法,你往哪里跳转(传递那些参数)
//第二个参数:成功的回调
//第三个参数,失败的回调
if(resolve && reject){
//call|apply区别
//相同点:都可以调用函数一次,都可以篡改函数的上下文一次
//不同点:call与apply传递参数:call传递参数用逗号隔开,apply方法执行,传递数组。
originPush.call(this,location,resolve,reject);
}else{
originPush.call(this,location,()=>,()=>);
}
}
2、Home模块组件拆分
--先把静态页面完成
--拆分出静态组件
--获取服务器的数据进行展示
--动态业务
3、三级联动组件完成
--由于三级联动,在Home、Search、Detail出现,把三级联动注册成一个全局组件
好处:只需要注册一次,就可以在项目的任何地方使用。
3.1 开始工作
1、在pages/Home文件夹中创建TypeNav文件夹,在其中创建index.vue文件。
<template>
<!-- 商品分类导航 -->
<div class="type-nav">
......
</div>
</template>
<script>
export default{
name:"TypeNav"
}
</script>
<style scoped lang="less">
.typle-nav{
.....
}
</style>
2、在main.js中注册全局组件
import Vue from 'vue'
import App from './App.vue'
//三级联动组件---全局组件
import TypeNav from '@/pages/Home/TypeNav'
//第一个参数:全局组件的名字,第二个参数:哪一个组件
Vue.component(TypeNav.name,TypeNav)
//引入路由
import router from '@/router'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
//注册路由信息:这里书写router的时候,组件身上拥有$route,$router属性
router
}).$mount('#app')
3、在Home的index.vue中使用
<template>
<div>
<!--三级联动全局组件:三级联动已经注册为全局组件,因此不需要引入-->
<TypeNav/>
</div>
</template>
4、完成其余静态组件
HTML+CSS+图片资源
ListContainer
在Home文件夹中创建ListContainer文件夹,再创建index.vue
<template>
<!--列表-->
<div class="list-container">
<div class="sortList clearfix">
<div class="center">
<!--banner轮播-->
<div class="swiper-container" id="mySwiper">
<div class="swiper-wrapper">
<div class="swiper-slide">
<img src="./images/banner1.jpg" />
</div>
<div class="swiper-slide">
<img src="./images/banner2.jpg" />
</div>
<div class="swiper-slide">
<img src="./images/banner3.jpg" />
</div>
<div class="swiper-slide">
<img src="./images/banner4.jpg" />
</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
<!-- 如果需要导航按钮 -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
</div>
<div class="right">
<div class="news">
<h4>
<em class="fl">尚品汇快报</em>
<span class="fr tip">更多 ></span>
</h4>
<div class="clearix"></div>
<ul class="news-list unstyled">
<li>
<span class="bold">[特惠]</span>备战开学季 全民半价购数码
</li>
<li>
<span class="bold">[公告]</span>备战开学季 全民半价购数码
</li>
<li>
<span class="bold">[特惠]</span>备战开学季 全民半价购数码
</li>
<li>
<span class="bold">[公告]</span>备战开学季 全民半价购数码
</li>
<li>
<span class="bold">[特惠]</span>备战开学季 全民半价购数码
</li>
</ul>
</div>
<ul class="lifeservices">
<li class=" life-item ">
<i class="list-item"></i>
<span class="service-intro">话费</span>
</li>
<li class=" life-item ">
<i class="list-item"></i>
<span class="service-intro">机票</span>
</li>
<li class=" life-item ">
<i class="list-item"></i>
<span class="service-intro">电影票</span>
</li>
<li class=" life-item ">
<i class="list-item"></i>
<span class="service-intro">游戏</span>
</li>
<li class=" life-item">
<i class="list-item"></i>
<span class="service-intro">彩票</span>
</li>
<li class=" life-item">
<i class="list-item"></i>
<span class="service-intro">加油站</span>
</li>
<li class=" life-item">
<i class="list-item"></i>
<span class="service-intro">酒店</span>
</li>
<li class=" life-item">
<i class="list-item"></i>
<span class="service-intro">火车票</span>
</li>
<li class=" life-item ">
<i class="list-item"></i>
<span class="service-intro">众筹</span>
</li>
<li class=" life-item">
<i class="list-item"></i>
<span class="service-intro">理财</span>
</li>
<li class=" life-item">
<i class="list-item"></i>
<span class="service-intro">礼品卡</span>
</li>
<li class=" life-item">
<i class="list-item"></i>
<span class="service-intro">白条</span>
</li>
</ul>
<div class="ads">
<img src="./images/ad1.png" />
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "ListContainer"
}
</script>
<style scoped lang="less">
相关样式资源
</style>
在ListContainer中创建images文件夹放置相关图片资源
在Home文件夹下的index.vue引入并且注册组件,之后引用组件
<template>
<div>
<!--三级联动全局组件:三级联动已经注册为全局组件,因此不需要import引入-->
<TypeNav/>
<!--引入组件-->
<ListContainer/>
</div>
</template>
<script>
//引入其余的组件
import ListContainer from '@/pages/Home/ListContainer';
//注册组件
export default {
name: "Home",
components: {
ListContainer
}
}
</script>
<style scoped>
</style>
Recommend
在Home文件夹中创建Recommend文件夹,再创建index.vue和images文件夹放置图片资源
之后在Home文件夹下的index.vue中进行组件的注册,引用
引入
import Recommend from '@/pages/Home/Recommend'
注册
export default {
name: "Home",
components: {
ListContainer,
Recommend
}
}
引用
<Recommend/>
Rank
Like\Floor\Brand
4、postman测试接口
开发服务器接口
http://gmall-h5-api.atguigu.cn
首页三级分类:
请求地址
/api/product/getBaseCategoryList
在postman中新建一个new request,测试是否能够成功
"code": 200,成功
http://gmall-h5-api.atguigu.cn/api/product/getBaseCategoryList
5、axios二次封装
5.1 为什么需要进行二次封装axios
请求拦截器:可以在发请求之前处理一些事务
响应拦截器:当服务器数据返回以后,可以处理一些事情
5.2 安装axios ----可以参考git|NPM关于axios文档
D:\项目\project-hph\app>cnpm install --save axios
在src文件夹中创建一个api文件夹【axios】,创建一个request.js文件
接口当中:路径都带有/api
baseURL:"/api"
//对axios进行二次封装,主要是用到它的请求和响应拦截器
//引入axios
import axios from "axios";
//1:利用axios对象的方法,去创建一个axios实例
//2:request就是axios,只不过稍微配置一下
const requests = axios.create({
//配置对象
// 基础路径,发请求的时候,路径当中会出现/api
baseURL:"/api",
//请求超时的时间,五秒
timeout:5000,
});
//请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情
requests.interceptors.request.use((config)=>{
// config:配置对象,对象里面有一个属性很重要:header请求头
return config
})
//响应拦截器
requests.interceptors.response.use((res)=>{
//成功回调的函数:服务器相应数据回来以后,响应拦截器可以检测到,可以做一些事情
return res.data;
},(error)=>{
// 响应失败的回调函数
return Promise.reject(new Error('fail'))
});
//对外暴露
export default axios;
6、API接口统一管理
项目很小:完全可以在组建的生命周期函数中发送请求
大:axios.get('xxx')
在api文件夹中创建index.js文件
//当前这个模块:对API进行统一管理
import requests from './request';
//三级联动的接口:/api/product/getBaseCategoryList GET请求 参数无
//对外暴露一个函数
export const reqCategoryList = ()=>
//发请求:axios发请求返回结果Promise对象
requests({
url:'/product/getBaseCategoryList' ,method:'get'
});
在main.js中进行测试
//测试
import {reqCategoryList} from '@/api';
reqCategoryList();
6.1 跨域问题
什么是跨域:协议、域名、端口号不同请求,称之为跨域
http://localhost:8080/#/home前端项目本地服务器
http://gmall-h5-api.atguigu.cn后台服务器
jsonp、cros、代理
在vue.config.js中配置代理
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave:false,/*关闭校验功能*/
devServer: {
host: 'localhost', port: 8080,
proxy:{
'/api':{
target:'http://gmall-h5-api.atguigu.cn',
// pathRewrite:{'^/api':''}
}
}
}
})
重启服务器
7、nprogress进度条的使用
在控制栏中安装
D:\项目\project-hph\app>cnpm install --save nprogress
nprogress方法
NProgress.start() // 进度条开始
NProgress.set() // 将进度设置到具体的百分比位置,取值范围是0到1.0
NProgress.inc() // 如果少量增加进度,进度将永远不会得到100%
NProgress.done() // 进度条结束消失
NProgress.configure() // 进度条参数配置
===================================================================================================
可以通过调用 .set(n)来设置进度,n是0-1的数字。
NProgress.set(0.0); // Sorta same as .start()
NProgress.set(0.4);
NProgress.set(1.0); // Sorta same as .done()
可以使用inc()随机增长进度条,注意,这个方法永远不会让进度条达到100%。
NProgress.inc();
NProgress.configure({ showSpinner: false }) //禁用进度环
NProgress.configure({ trickle : false }) //关闭进度条步进
NProgress.configure({ trickleRate : 10}) //每次步进增长多少
NProgress.configure({ trickleSpeed : 200}) //每次步进间隔,单位毫秒ms
NProgress.configure({ easing: 'linear'}) //动画方向
NProgress.configure({ speed : 10}) //动画速度,单位毫秒ms
NProgress.configure({ minimum : 0.08}) //最小百分比
进度条在request.js中使用
//对axios进行二次封装,主要是用到它的请求和响应拦截器
//引入axios
import axios from "axios";
//引入进度条
import nprogress from 'nprogress';
//引入进度条样式
import "_nprogress@0.2.0@nprogress/nprogress.css"
//start:进度条开始 done:进度条结束
//1:利用axios对象的方法,去创建一个axios实例
//2:request就是axios,只不过稍微配置一下
const requests = axios.create({
//配置对象
// 基础路径,发请求的时候,路径当中会出现/api
baseURL:"/api",
//请求超时的时间,五秒
timeout:5000,
});
//请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情
requests.interceptors.request.use((config)=>{
// config:配置对象,对象里面有一个属性很重要
// 进度条开始动
nprogress.start();
return config;
})
//响应拦截器
requests.interceptors.response.use((res)=>{
//成功回调的函数:服务器相应数据回来以后,响应拦截器可以检测到,可以做一些事情
// 进度条结束
nprogress.done();
return res.data;
},(error)=>{
// 响应失败的回调函数
return Promise.reject(new Error('fail'))
});
//对外暴露
export default requests;
8、vuex状态管理库
8.1 vuex
vuex是官方提供的一个插件,状态管理库,集中式管理项目中组件公用的数据。
项目大,组件很多,数据很多,数据维护麻烦,需要使用
vue2项目需要用vuex 3版本
D:\项目\project-hph\app>cnpm install --save vuex 安装最新版本的
D:\项目\project-hph\app>npm install vuex@3.6.2 指定版本安装
8.2 vuex的基本使用
在src文件夹下,创建store文件夹,在其中创建index.js文件
import Vue from 'vue';
import Vuex from 'vuex';
//需要使用vuex一次
Vue.use(Vuex);
// state:仓库存储数据的地方
const state = {};
// mutations:修改state的唯一手段
const mutations = {};
// actions:处理action,可以书写自己的业务逻辑,也可以处理异步
const actions = {};
// getters:理解为计算属性,用于简化仓库数据,让组件获取仓库更加方便
const getters = {};
//对外暴露Store类的一个实例
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
在mainjs中引入仓库
import Vue from 'vue'
import App from './App.vue'
//三级联动组件---全局组件
import TypeNav from '@/pages/Home/TypeNav'
//第一个参数:全局组件的名字,第二个参数:哪一个组件
Vue.component(TypeNav.name,TypeNav);
//引入路由
import router from '@/router'
Vue.config.productionTip = false
//引入仓库
import store from '@/store';
//测试
import {reqCategoryList} from '@/api';
reqCategoryList();
new Vue({
render: h => h(App),
//注册路由信息:这里书写router的时候,组件身上拥有$route,$router属性
router,
// 注册仓库:
store
}).$mount('#app')
8.3 使用:实现计数器的使用
在Home文件夹下的index.vue中先将所有引用的组件注释了
<template>
<div>
<button>点击我加1</button>
<span>仓库的数据{{count}}</span>
<button>点击我减1</button>
</div>
</template>
<script>
//引入其余的组件
import ListContainer from '@/pages/Home/ListContainer';
import Recommend from '@/pages/Home/Recommend'
import Rank from '@/pages/Home/Rank'
import Like from '@/pages/Home/Like'
import Floor from '@/pages/Home/Floor'
import Brand from '@/pages/Home/Brand'
//注册组件
export default {
name: "Home",
components: {
ListContainer,
Recommend,
Rank,
Like,
Floor,
Brand
}
}
</script>
<style scoped>
</style>
去仓库store中存放数据的地方
import Vue from 'vue';
import Vuex from 'vuex';
//需要使用vuex一次
Vue.use(Vuex);
// state:仓库存储数据的地方
const state = {
count:1
};
// mutations:修改state的唯一手段
const mutations = {
};
// actions:处理action,可以书写自己的业务逻辑,也可以处理异步
const actions = {
};
// getters:理解为计算属性,用于简化仓库数据,让组件获取仓库更加方便
const getters = {
};
//对外暴露Store类的一个实例
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
刚在index.vue中写的组件想要获取,仓库的数据。需要用到咱们的辅助函数
<template>
<div>
<!-- 绑定单击事件-->
<button @click="add">点击我加1</button>
<span>仓库的数据{{count}}</span>
<button>点击我减1</button>
</div>
</template>
<script>
//引入其余的组件
import ListContainer from '@/pages/Home/ListContainer';
import Recommend from '@/pages/Home/Recommend'
import Rank from '@/pages/Home/Rank'
import Like from '@/pages/Home/Like'
import Floor from '@/pages/Home/Floor'
import Brand from '@/pages/Home/Brand'
//辅助函数mapstate :映射为组件身上的数据
import {mapState} from 'vuex';
//注册组件
export default {
name: "Home",
components: {
ListContainer,
Recommend,
Rank,
Like,
Floor,
Brand
},
computed:{
...mapState(['count'])
}
}
</script>
<style scoped>
</style>
给button绑定单击事件
<!-- 绑定单击事件-->
<button @click="add">点击我加1</button>
绑定事件后,需在methods中要写相关的方法,和派发action
<template>
<div>
<!-- 绑定单击事件-->
<button @click="add">点击我加1</button>
<span>仓库的数据{{count}}</span>
<button>点击我减1</button>
</div>
</template>
<script>
//引入其余的组件
import ListContainer from '@/pages/Home/ListContainer';
import Recommend from '@/pages/Home/Recommend'
import Rank from '@/pages/Home/Rank'
import Like from '@/pages/Home/Like'
import Floor from '@/pages/Home/Floor'
import Brand from '@/pages/Home/Brand'
//辅助函数mapstate :映射为组件身上的数据
import {mapState} from 'vuex';
//注册组件
export default {
name: "Home",
components: {
ListContainer,
Recommend,
Rank,
Like,
Floor,
Brand
},
computed:{
...mapState(['count'])
},
methods:{
add(){
// 派发action
this.$store.dispatch('add')
}
}
}
</script>
<style scoped>
</style>
派发ation后需要回到store的index.js中的actions中
import Vuex from 'vuex';
//需要使用vuex一次
Vue.use(Vuex);
// state:仓库存储数据的地方
const state = {
count:1
};
// mutations:修改state的唯一手段
const mutations = {
ADD(state){
state.count++;
}
};
// actions:处理action,可以书写自己的业务逻辑,也可以处理异步
const actions = {
// 这里可以书写业务逻辑,但是不能修改state
add({commit}){
commit("ADD");
}
};
// getters:理解为计算属性,用于简化仓库数据,让组件获取仓库更加方便
const getters = {
};
//对外暴露Store类的一个实例
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
8.4 vuex模块式开发
在store文件夹下创建,所有组建的对应名字的小仓库和index.js文件
之后需要将俩个小仓库合并到大仓库中
在大仓库中引入两个小仓库
import Vue from 'vue';
import Vuex from 'vuex';
//需要使用vuex一次
Vue.use(Vuex);
//引入小仓库
import home from './home';
import search from './search';
//对外暴露Store类的一个实例
export default new Vuex.Store({
// 实现vuex仓库模块化开发存储数据
modules:{
home,
search
}
})
9、完成TypeNav三级联动展示数据业务
将TypeNav文件夹放置到components文件夹中,别忘了将main.js路径修改一下
<teamplate>
<div class="type-nav">
</div>
</teamplate>
<script>
export default {
name: "TypeNav",
// 组件挂载完毕:可以向服务器发请求
mounted() {
// 通知vuex发请求,获取数据,存储于仓库当中
this.$store.dispatch('categoryList');
// 找到相应的home仓库,去书写对应的action
}
}
</script>
<style>
</style>
在home的index.js中
import {reqCategoryList} from '@/api';
//home模块的小仓库
const state = {
//state中数据默认初始值要与服务器中的一致。服务器返回对象,服务器返回数组。(根本接口的返回值进行初始化)
categoryList:[],
};
const mutations = {
CATEGORYLIST(state,categoryList){
state.categoryList = categoryList;
}
};
const actions = {
//通过api里面的接口函数调用,向服务器发请求,获取服务器的数据
async categoryList({commit}){
let result = await reqCategoryList();
console.log(result);
if(result.code==200){
commit("CATEGORYLIST",result.data);
}
}
};
const getters = {
};
export default {
state,
mutations,
actions,
getters
}
{{commit}}是结构赋值的结果
dispatch是异步操作,用action接收,actions出来的是promise,需要用async 和 await进行异步操作
<template>
<div class="type-nav">
</div>
</template>
<script>
import {mapState} from 'vuex';
export default {
name: "TypeNav",
// 组件挂载完毕:可以向服务器发请求
mounted() {
// 通知vuex发请求,获取数据,存储于仓库当中
this.$store.dispatch('categoryList');
// 找到相应的home仓库,去书写对应的action
},
computed:{
...mapState({
//右侧需要的是一个函数,当使用这个计算属性的时候,右侧函数会立即执行一次
//注入一个参数state,其实即为大仓库中的数据
categoryList:state=>state.home.categoryList
})
}
}
</script>
<style scope lang="less">
</style>
之后在TypeNavd的index.vue中iu修改相关数据
<template>
<!-- 商品分类导航 -->
<div class="type-nav">
<div class="container">
<h2 class="all">全部商品分类</h2>
<nav class="nav">
<a href="#">服装城</a>
<a href="#">美妆馆</a>
<a href="#">尚品汇超市</a>
<a href="#">全球购</a>
<a href="#">闪购</a>
<a href="#">团购</a>
<a href="#">有趣</a>
<a href="#">秒杀</a>
</nav>
<div class="sort">
<div class="all-sort-list2">
<!--遍历一级菜单c1,以及索引值index,在你计算出来的属性categoryList中遍历 :key为一级分类的categoryId-->
<div class="item" v-for="(c1,index) in categoryList" :key="c1.categoryId">
<h3>
<!--一级分类:这里不能写死,要写成一级分类的名字,从和一级菜单中的categoryName中获取-->
<a href="">{{c1.categoryName}}</a>
</h3>
<div class="item-list clearfix">
<!--二级分类:便利的是一级分类下的categoryChild-->
<div class="subitem" v-for="(c2,index) in c1.categoryChild" :key="c2.categoryId">
<dl class="fore">
<!--二级分类的名字替换:-->
<dt>
<a href="">{{c2.categoryName}}</a>
</dt>
<dd>
<!--三级分类:-->
<em v-for="(c3,index) in c2.categoryChild" :key="c3.categoryId">
<a href="">{{c3.categoryName}}</a>
</em>
</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {mapState} from 'vuex';
export default {
name: "TypeNav",
// 组件挂载完毕:可以向服务器发请求
mounted() {
// 通知vuex发请求,获取数据,存储于仓库当中
this.$store.dispatch('categoryList');
// 找到相应的home仓库,去书写对应的action
},
computed:{
...mapState({
//右侧需要的是一个函数,当使用这个计算属性的时候,右侧函数会立即执行一次
categoryList:state=>state.home.categoryList
})
}
}
</script>
<style scope lang="less">
</style>
注意分析结构
[
{
id:1,
name:'电子书',
child:[
{
id:2,
name:"喜羊羊",
child:[
{
id:3,
name:"灰太狼"
}
]
}
]
}
]
10、完成一级分类动态添加背景颜色
10.1 第一种解决方案:采用样式解决
.item:hover{
background: deepskyblue;
}
10.2 第二种解决方案:通过js完成
1、鼠标移上去时背景颜色会变蓝
<!--遍历一级菜单c1,以及索引值index,在你计算出来的属性categoryList中遍历 :key为一级分类的categoryId 当currentIndex==index值相等时,类名为cur-->
<div class="item" v-for="(c1,index) in categoryList" :key="c1.categoryId" :class="{cur:currentIndex==index}" >
<!-- @mouseente鼠标接触 changeIndex:回调 需要传参 在methods中写一个方法-->
<h3 @mouseenter="changeIndex(index)">
<!--一级分类:这里不能写死,要写成一级分类的名字,从和一级菜单中的categoryName中获取-->
<a href="">{{c1.categoryName}}--{{index}}</a>
</h3>
<script>
import {mapState} from 'vuex';
export default {
name: "TypeNav",
data(){
return{
//存储用户鼠标移上哪个一级分类的索引值
currentIndex:-1
}
},
// 组件挂载完毕:可以向服务器发请求
mounted() {
// 通知vuex发请求,获取数据,存储于仓库当中
this.$store.dispatch('categoryList');
// 找到相应的home仓库,去书写对应的action
},
computed:{
...mapState({
//右侧需要的是一个函数,当使用这个计算属性的时候,右侧函数会立即执行一次
categoryList:state=>state.home.categoryList
})
},
methods:{
//鼠标进入修改响应式数据currentIndex属性 (index)接受index的值
changeIndex(index){
// index:鼠标移动到某一个一级分类上时,一级分类元素的索引值
this.currentIndex = index;
}
},
};
</script>
在.all-sort-list2中书写样式代码
.cur{
background: lightskyblue;
}
2、鼠标移动走之后颜色会变回去
<!-- @mouseente鼠标接触 changeIndex:回调 需要传参 在methods中写一个方法-->
<h3 @mouseenter="changeIndex(index)" @mouseleave="leaveIndex">
methods:{
//鼠标进入修改响应式数据currentIndex属性 (index)接受index的值
changeIndex(index){
// index:鼠标移动到某一个一级分类上时,一级分类元素的索引值
this.currentIndex = index;
},
// 一级分类鼠标移除的事件回调
leaveIndex(){
this.currentIndex = -1
}
},
事件委派
此时当鼠标完全离开一级菜单和标题时,背景颜色才会变成白色。
<div @mouseleave="leaveIndex">
<h2 class="all">全部商品分类</h2>
<div class="sort">
<div class="all-sort-list2">
...........
</div>
</div>
</div>
11、通过JS二三级商品分类的显示与隐藏
最开始的时候,是通过CSS样式display: block|none显示与隐藏二三级商品分类
<!--二三级分类的地方-->
<div class="item-list clearfix" :style="{display:currentIndex==index?'block':'none'}">
12、演示卡顿现象:节流和防抖
正常:事件触发非常频繁,而且每一次触发,回调函数都要去执行(如果时间很短,而回调函数内部有计算,那么可能出现浏览器卡顿)
节流:在规定的间隔范围内不会触发回调,只有大于这个时间间隔才会触发回调,把频繁触发变为少量触发。技能有cd,在规定的时间里点几次。
<html>
<body>
<div>
<h1>
我是计数器
<span>0</span>
<button>
点击我加上1
</button>
</h1>
</div>
</body>
</html>
<script>
let span = document.querySelector('span');
let button = document.querySelector('button');
let count = 0;
//计数器在一秒以内,数字只能加上1
button.onclick = _.throttle(function(){
count++;
span.innerHTML = count;
},1000)
</script>
防抖:前面的所有的触发都被取消,最后一次执行在规定的事件之后才会触发,也就说如果连续快速的触发,只会执行一次。回城你扣b使劲扣,也就是触发一次回城
鼠标移动速度过快,然后菜单的index只能显示几个
13、完成三级联动节流的操作
<script>
import {mapState} from 'vuex';
//引入lodash import _ from 'lodash'
//单独引入相关js函数
import throttle from 'lodash/throttle';
export default {
name: "TypeNav",
data(){
return{
//存储用户鼠标移上哪个一级分类的索引值
currentIndex:-1
}
},
// 组件挂载完毕:可以向服务器发请求
mounted() {
// 通知vuex发请求,获取数据,存储于仓库当中
this.$store.dispatch('categoryList');
// 找到相应的home仓库,去书写对应的action
},
computed:{
...mapState({
//右侧需要的是一个函数,当使用这个计算属性的时候,右侧函数会立即执行一次
categoryList:state=>state.home.categoryList
})
},
methods:{
//鼠标进入修改响应式数据currentIndex属性 (index)接受index的值
//此处不能用箭头函数
changeIndex:throttle(function (index){
// index:鼠标移动到某一个一级分类上时,一级分类元素的索引值
// 正常操作:鼠标慢慢进入,每一个一级分类h3,都会触发鼠标进入事件
// 非正常情况:鼠标进入很快,本身的一级分类都应该触发鼠标进入事件,但是经过测试,只有部分h3出发了
// 由于用户的行为过快,导致浏览器反应不过来,如果当前回调函数中,有一些大量的业务,有可能出现卡顿现象。
this.currentIndex = index;
},50),
// 一级分类鼠标移除的事件回调
leaveIndex(){
this.currentIndex = -1
}
},
};
</script>
14、三级联动组件的路由与传递参数
三级联动用户可以点击的:一级分类,二级分类,三级分类,当你点击的时候
Home模块跳转到Search模块。以及会把用户选中的产品(产品的名字和ID)在路由跳转的时候,进行传递。
路由跳转:
声明式导航:router-link 耗内存,卡顿
<router-link to
编程式导航:push|replace
<a @click="goSearch"></a>
goSearch(){
this.$router.push('/search')
}
编程式导航加上事件的委派
15、完成三级联动的路由跳转和传递参数
<template>
<!-- 商品分类导航 -->
<div class="type-nav">
<div class="container">
<!--事件委派|事件委托-->
<div @mouseleave="leaveIndex">
<h2 class="all">全部商品分类</h2>
<!--三级联动-->
<div class="sort">
<!-- 利用事件委派+编程式导航实现路由的跳转与传递参数-->
<div class="all-sort-list2" @click="goSearch">
<!--遍历一级菜单c1,以及索引值index,在你计算出来的属性categoryList中遍历 :key为一级分类的categoryId-->
<div class="item" v-for="(c1,index) in categoryList" :key="c1.categoryId" :class="{cur:currentIndex==index}" >
<!-- @mouseente鼠标接触 changeIndex:回调 需要传参 在methods中写一个方法-->
<h3 @mouseenter="changeIndex(index)">
<!--一级分类:这里不能写死,要写成一级分类的名字,从和一级菜单中的categoryName中获取-->
<a :data-categoryName="c1.categoryName" :data-category1Id="c1.categoryId">{{c1.categoryName}}</a>
</h3>
<!--二三级分类的地方-->
<div class="item-list clearfix" :style="{display:currentIndex==index?'block':'none'}">
<!--二级分类:便利的是一级分类下的categoryChild-->
<div class="subitem" v-for="(c2,index) in c1.categoryChild" :key="c2.categoryId">
<dl class="fore">
<!--二级分类的名字替换:-->
<dt>
<a :data-categoryName="c2.categoryName" :data-category2Id="c2.categoryId">{{c2.categoryName}}</a>
</dt>
<dd>
<!--三级分类:-->
<em v-for="(c3,index) in c2.categoryChild" :key="c3.categoryId">
<a :data-categoryName="c3.categoryName" :data-category3Id="c3.categoryId">{{c3.categoryName}}</a>
</em>
</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
</div>
<nav class="nav">
<a href="#">服装城</a>
<a href="#">美妆馆</a>
<a href="#">尚品汇超市</a>
<a href="#">全球购</a>
<a href="#">闪购</a>
<a href="#">团购</a>
<a href="#">有趣</a>
<a href="#">秒杀</a>
</nav>
</div>
</div>
</template>
<script>
import {mapState} from 'vuex';
//引入lodash
import throttle from 'lodash/throttle';
export default {
name: "TypeNav",
data(){
return{
//存储用户鼠标移上哪个一级分类的索引值
currentIndex:-1
}
},
// 组件挂载完毕:可以向服务器发请求
mounted() {
// 通知vuex发请求,获取数据,存储于仓库当中
this.$store.dispatch('categoryList');
// 找到相应的home仓库,去书写对应的action
},
computed:{
...mapState({
//右侧需要的是一个函数,当使用这个计算属性的时候,右侧函数会立即执行一次
categoryList:state=>state.home.categoryList
})
},
methods:{
//鼠标进入修改响应式数据currentIndex属性 (index)接受index的值
changeIndex:throttle(function (index){
// index:鼠标移动到某一个一级分类上时,一级分类元素的索引值
// 正常操作:鼠标慢慢进入,每一个一级分类h3,都会触发鼠标进入事件
// 非正常情况:鼠标进入很快,本身的一级分类都应该触发鼠标进入事件,但是经过测试,只有部分h3出发了
// 由于用户的行为过快,导致浏览器反应不过来,如果当前回调函数中,有一些大量的业务,有可能出现卡顿现象。
this.currentIndex = index;
},50),
// 一级分类鼠标移除的事件回调
leaveIndex(){
this.currentIndex = -1
},
//进行路由的跳转
goSearch(event){
//事件委派,是把全部的子节点【h3、dt、dl、em】的事件委派给父节点
//点击a标签的时候,才会进行路由的跳转(怎么确定是目标a标签)
//存在另外一个问题:即使你能确定点击的是a标签,如何区分是一级,二级,三级分类的标签
let element = event.target;
//获取到当前触发的这个事件的节点【h3,a,dt,dl】,需要带有data-categoryname这样的节点【一定是a标签了】
//节点有一个属性dataset属性,可以获取节点的自定义属性与属性值
let {categoryname,category1id,category2id,category3id} =element.dataset;
//如果标签身上有categoryname一定是a标签
if (categoryname){
//整理路由跳转的参数
let location = {name:'search'};
let query = {categoryName:categoryname};
//一级分类、二级分类、三级分类的a标签
if(category1id){
query.category1Id = category1id;
}else if(category2id){
query.category2Id = category2id;
}else{
query.category3Id = category3id;
}
//整理完参数
location.query = query;
//路由的跳转
this.$router.push(location);
}
}
},
};
</script>

浙公网安备 33010602011771号