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>
posted @ 2022-09-27 16:35  孤舟蓑衣客  阅读(16)  评论(0)    收藏  举报