Day3

1、Search模块中商品分类与过渡动画

过渡动画:前提条件是组件或者元素必须要有v-if v-show指令才可以进行过渡动画

<template>
<!--商品分类导航-->
  <div class="type-nav">
    <div class="container">
<!--事件委派-->
      <div @mouseleave="leaveShow" @mouseenter="enterShow">
        <h2 class="all">全部商品分类</h2>
        <!-- 过渡动画 -->
        <transition name="sort">
<!--          三级联动-->
          <div class="sort" v-show="show">
<!--            利用事件的委派+编程式导航实现路由的跳转与传递参数-->
            <div class="all-sort-list2" @click="goSearch">
<!--              遍历一级菜单,以及索引值index,在计算属性categoryList中遍历  :key为一级分类的categoryId-->
              <div
                  class="item"
                  v-for="(c1, index) in categoryList"
                  :key="c1.categoryId"
              >
<!--             @mouseenter鼠标接触时调用changeIndex方法,需要传参数index  在methods中写对应的方法
                  :class="{ cur: currentIndex == index }"意思是当值相等时,h3类名叫cur-->
                <h3
                    :class="{ cur: currentIndex == index }"
                    @mouseenter="changeIndex(index)"
                >
<!--                  一级分类:这里不能写死,需要写一级分类的名字和ID,从一级菜单中的categoryName,categoryId中获取-->
                  <a
                      :data-categoryName="c1.categoryName"
                      :data-category1Id="c1.categoryId"
                  >{{ c1.categoryName }}</a
                  >
                </h3>
<!--                二三级分类,当值相等的时候:none	此元素不会被显示。
                            block	此元素将显示为块级元素,此元素前后会带有换行符。-->
                <div
                    class="item-list clearfix"
                    :style="{ display: currentIndex == index ? 'block' : 'none' }"
                >
<!--                  二级分类:遍历的是一级分类下的categoryChild-->
                  <div class="subitem">
                    <dl
                        class="fore"
                        v-for="(c2, index) in c1.categoryChild"
                        :key="c2.categoryId"
                    >
                      <dt>
<!--                        二级分类的名字和id-->
                        <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>
        </transition>
      </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:是把lodash全部封装好的函数全都引入进来了
//按需引入:只是引入节流函数,其他的函数没有引入(模块),这样做的好处是,当你打包项目的时候体积会小一些
import throttle from "lodash/throttle";
export default {
  name: "TypeNav",
  data() {
    return {
      //当前这个属性决定了到底那个h3身上有类名
      currentIndex: -1,
      show: true,
    };
  },
  //发请求经常在mounted生命周期函数中进行
  //组件挂载完毕
  mounted() {
    //  通知vuex发请求,获取数据,存储于仓库当中
    this.$store.dispatch('categoryList');
    //当组件挂载完毕,让show属性变为false
    //如果不是Home路由组件,将typeNav进行隐藏
    if (this.$route.path != '/home') {
      this.show = false;
    }
  },
  //计算属性
  computed: {
    //state:他是咱们大仓库中的state(包含home|search)
    //右侧需要的是一个函数,当使用这个计算属性的时候,右侧函数会立即执行一次
    ...mapState({
      categoryList: state => state.home.categoryList,
    }),
  },
  methods: {
    //鼠标进入修改响应式数据currentIndex属性   (index)接受index的值
    //当用户鼠标移入到h3身上的时候就会立即出发一次
    changeIndex: throttle(function(index) {
      //  index:鼠标移动到某一个一级分类上时,一级分类元素的索引值
      //  正常操作:鼠标慢慢进入,每一个一级分类h3,都会触发鼠标进入事件
      //  非正常情况:鼠标进入很快,本身的一级分类都应该触发鼠标进入事件,但是经过测试,只有部分h3出发了
      //  由于用户的行为过快,导致浏览器反应不过来,如果当前回调函数中,有一些大量的业务,有可能出现卡顿现象。
      //函数节流:在20MS时间之内只能执行一次
      this.currentIndex = index;
    }, 20),
    //进行路由跳转的回调函数
    goSearch(event) {
      //event.target:获取到的是出发事件的元素(div、h3、a、em、dt、dl)
      let element = event.target;
      //给a标签添加自定义属性data-categoryName,全部的字标签当中只有a标签带有自定义属性.
      //节点有一个属性dataset属性,可以获取节点的自定义属性与属性值
      let {
        categoryname,
        category1id,
        category2id,
        category3id,
      } = element.dataset;
      //第二个问题解决了:点击的到底是不是a标签(只要这个标签身上带有categoryname)一定是a标签
      //当前这个if语句:一定是a标签才会进入
      if (categoryname) {
        //准备路由跳转的参数对象
        let location = { name: "search" };
        let query = { categoryName: categoryname };
        //一定是a标签:一级目录
        if (category1id) {
          query.category1Id = category1id;
          //一定是a标签:二级目录
        } else if (category2id) {
          query.category2Id = category2id;
          //一定是a标签:三级目录
        } else {
          query.category3Id = category3id;
        }
        //判断:如果路由跳转的时候,带有params参数,捎带脚传递过去
        if (this.$route.params) {
          location.params = this.$route.params;
          //动态给location配置对象添加query属性
          location.query = query;
          //路由跳转
          this.$router.push(location);
        }
      }
    },
    //当鼠标移入的时候,让商品分类列表进行展示
    enterShow() {
      if (this.$route.path != "/home") {
        this.show = true;
      }
    },
    //当鼠标离开的时候,让商品分类列表进行隐藏
    leaveShow() {
      this.currentIndex = -1;
      //判断如果是Search路由组件的时候才会执行
      if (this.$route.path != "/home") {
        this.show = false;
      }
    },
  },
};
</script>

<style scoped lang="less">
.type-nav {
  border-bottom: 2px solid #e1251b;

  .container {
    width: 1200px;
    margin: 0 auto;
    display: flex;
    position: relative;

    .all {
      width: 210px;
      height: 45px;
      background-color: #e1251b;
      line-height: 45px;
      text-align: center;
      color: #fff;
      font-size: 14px;
      font-weight: bold;
    }

    .nav {
      a {
        height: 45px;
        margin: 0 22px;
        line-height: 45px;
        font-size: 16px;
        color: #333;
      }
    }

    .sort {
      position: absolute;
      left: 0;
      top: 45px;
      width: 210px;
      height: 461px;
      position: absolute;
      background: #fafafa;
      z-index: 999;

      .all-sort-list2 {
        .item {
          h3 {
            line-height: 30px;
            font-size: 14px;
            font-weight: 400;
            overflow: hidden;
            padding: 0 20px;
            margin: 0;

            a {
              color: #333;
            }
          }

          .item-list {
            display: none;
            position: absolute;
            width: 734px;
            min-height: 460px;
            background: #f7f7f7;
            left: 210px;
            border: 1px solid #ddd;
            top: 0;
            z-index: 9999 !important;

            .subitem {
              float: left;
              width: 650px;
              padding: 0 4px 0 8px;

              dl {
                border-top: 1px solid #eee;
                padding: 6px 0;
                overflow: hidden;
                zoom: 1;

                &.fore {
                  border-top: 0;
                }

                dt {
                  float: left;
                  width: 54px;
                  line-height: 22px;
                  text-align: right;
                  padding: 3px 6px 0 0;
                  font-weight: 700;
                }

                dd {
                  float: left;
                  width: 415px;
                  padding: 3px 0 0;
                  overflow: hidden;

                  em {
                    float: left;
                    height: 14px;
                    line-height: 14px;
                    padding: 0 8px;
                    margin-top: 5px;
                    border-left: 1px solid #ccc;
                  }
                }
              }
            }
          }
        }
        .cur{
          background: lightskyblue;
        }

      }
    }
  }
  //  过渡动画的样式
//  过渡动画的开始状态,进入
  .sort-enter{
    height: 0px;
  }
//  过渡动画的结束状态(进入)
  .sort-enter-to{
    height: 461px;
  }
//定义动画事件、速率
  .sort-enter-active{
    transition: all .5s linear;
  }
}
</style>

三级列表的优化

在app根组件中发送请求【根组件mounted】执行一次

this.$store.dispatch("getCategoryList")放入到App.vue中,这样子mounted只会执行一次

2、合并参数params与query

Header.vue

  methods:{
    //搜索按钮的回调函数:需要向search进行跳转
    goSearch(){
      //路由传递参数:
      //第一种:字符串形式
      //this.$router.push('/search/'+this.keyword+"?k="+this.keyword.toUpperCase());
      //第二种:模板字符串
      //this.$router.push('/search/${this.keyword}?k=${this.keyword.toUpperCase}')
      //第三种:对象
      //代表的是如果有query参数也带过去
      if (this.$route.query) {
        let location = {
          name: "search",
          params: { keyword: this.keyword || undefined }
        };
        location.query = this.$route.query;
        this.$router.push(location);
      }
    }
  },

TypeNav index.vue

     //判断:如果路由跳转的时候,带有params参数,捎带脚传递过去
        if (this.$route.params) {
          location.params = this.$route.params;
          //动态给location配置对象添加query属性
          location.query = query;
          //路由跳转
          this.$router.push(location);
        }

3、开发Home首页当中ListContainer组件与floor组件

服务器返回的数据无轮播图和底部的数据,需要用到mock模拟:用插件mock.js

D:\项目\project-hph\app>cnpm install --save mockjs

使用步骤

1、在src文件夹下创建mock文件夹

2、准备JSON数据(mock文件夹中创建相应的json文件)--别留有空格

3、把mock数据需要的图片放置到public文件夹中【public文件夹再打包的时候,会把对应的资源原封不动的打包到dist文件夹中】

4、开始mock,通过mockjs模块实现

5、在mock文件夹下创建mockServe.js文件,通过mock.js插件实现模拟数据

//先引入mockjs模块
import Mock from 'mockjs';
//把json数据引入进来,图片和json数据格式默认对外暴露
import banner from './banners.json';
import floor from './floors.json';
//mock数据:第一个参数:请求地址  第二个参数:请求数据
Mock.mock("/mock/banner",{code:200,data:banner});//模拟轮播图
Mock.mock("/mock/floor",{code:200,data:floor})

6、mockServe.js文件在入口mian.js文件中引入

//引入mockServe.js
import '@/mock/mockSerce';

7、在api文件夹中创建mockAjax.js文件,用来发送请求---路径需要修改成/mock

//对axios进行二次封装,主要是用到它的请求和响应拦截器

//引入axios
import axios from "axios";

//引入进度条
import nprogress from 'nprogress';
//引入进度条样式
import "nprogress/nprogress.css"
//start:进度条开始 done:进度条结束




//1:利用axios对象的方法,去创建一个axios实例
//2:request就是axios,只不过稍微配置一下
const  requests = axios.create({
    //配置对象
    // 基础路径,发请求的时候,路径当中会出现/api
    baseURL:"/mock",

    //请求超时的时间,五秒
    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、发请求

//当前这个模块:对API进行统一管理
import requests from './request';
import mockRequest from './mockAjax';
//三级联动的接口:/api/product/getBaseCategoryList  GET请求  参数无

//对外暴露一个函数,只要外部调用这个函数,就向服务器发送ajax请求、获取咱们的三级菜单数据。当前这个函数只需要把服务器返回的结果返回即可
//发请求:axios发请求返回结果Promise对象
export const reqCategoryList = ()=>requests({url:'/product/getBaseCategoryList' ,method:'get'});


//获取banner(home首页轮播图接口)
export const reqGetBannerList= () =>mockRequest.get('/banner');

在listcontainer中index.vue发请求

mounted(){
//派发action:通过Vuex发起ajax请求,将数据存储在仓库中
  this.$sotre.dispatch('getBannerList' )
}

在home.js中的state,mutation,action中

import {reqgetCategoryList,reqGetBannerList} from "@/api";
//home模块的仓库
const state = {
  categoryList:[],
  bannerList:[]
};
//mutation是修改state的地方
const mutation = {
  GETCATEGORYLIST(state,categoryList){
    state.categoryList = categoryList;
  },
  GETBANNERLIST(state,bannerList){
    state.bannerList = bannerList;
  }
};
const action ={
  async getCategoryList({commit}){
    let result = await reqCategoryList();
    if(result.code==200){
      commit('GETCategoryList',result.data);
    }
  },
  async getBannerList({commit}){
    let result = await reqGetBannerList();
    if(result.code==200){
      commit('GETBANNERLIST',result.data);  //开始写mutation
    }
  },
};
const getters = {
  
};
export default{
  state,
  mutation,
  action,
  getters,
};

在ListContainer中的vue

<script>
	import {mapState} from 'vuex';
  export default{
    name:"",
    mounted(){
      this.$store.dispatch('getBannerList');
    },
    computed:{
      ...mapState({
        bannerList:state=>state.home.bannerList;
      })
    }
  }
</script>

3.1 listcontainer组件开发

安装swiper插件

cnpm install --save swiper@5

在轮播图vue中引入

import Swiper from 'swiper';

考虑到其他地方有相同的轮播图,这边可以把引入样式,放进入口文件main.js

//引入swiper样式文件
import "swiper/css/swiper.css";

 mounted() {
  //  派发action:通过Vuex发起ajax秦桧去,将数据存储在仓库中
    this.$store.dispatch('getBannerList');
  //  在new Swiper实例之前,页面中结构必须有【new Swiper 实例放在这里发现不行】
  //  因为dispatch当中涉及到异步语句,导致v-for遍历时结构还没有完全ok
  可以用延迟器试试
  settimeout(()=>{
  
  })
  },

第一种解决方案

//  在new Swiper实例之前,页面中结构必须有【new Swiper 实例放在这里发现不行】
//  因为dispatch当中涉及到异步语句,导致v-for遍历时结构还没有完全ok
  setTimeout(()=>{
    var mySwiper = new Swiper(document.querySelector(".swiper-container"),{
      loop:true,
    //  如果需要分页器
      pagination:{
        el:".swiper-pagination",
      //  点击小球的时候页切换图片
        clickable:true
      },
    //  如果需要前进回退按钮
      navigation:{
        nextEl:".swiper-button-next",
        prevEl:".swiper-button-prev",
      },
    });
  },1000);  

完美解决方案:

watch + nextTick:数据监听:监听已有数据的变化

nextTick:在下次 DOM 更新【原本bannerList是个空数组,之后数组里面有四个元素】循环结束之后【v-for循环结束】执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

  watch:{
  //  监听bannerList数据的变化-因为这条数据发生过变化--有空数组变为数组里面的四个元素
    bannerList:{
      handler(newValue,oldValue){
      //  现在咱们通过watch监听bannerList属性的属性值的变化
      //  如果执行handler方法,代表组件实例身上这个属性的属性已经有了【数组:四个元素】
      //  当这个函数执行:只能保证bannerList数据有了,此时v-for遍历需要时间,可能此时还没渲染好没有执行结束,
      //  v-for执行完毕你才能有结构
       //nextTick:在下次DOM更新循环结束之后,执行延迟回调,在修改数据之后立刻使用这个方法,获取更新后的DOM
        this.$nextTick(()=>{
        //  当你执行这个毁掉的时候:保证服务器数据据回来了,v-for执行完毕了【轮播图结构一定有了】
          var mySwiper = new Swiper(document.querySelector(".swiper-container"),{
            loop:true,
            //  如果需要分页器
            pagination:{
              el:".swiper-pagination",
              //  点击小球的时候页切换图片
              clickable:true
            },
            //  如果需要前进回退按钮
            navigation:{
              nextEl:".swiper-button-next",
              prevEl:".swiper-button-prev",
            },
          });
        })
      },
    },

  },

4、获取floor组件的mock数据

静态组件---写api-像服务器发请求*---写vuex的三连环---组件捞数据,展示数据

1、在api文件夹中的index.js

//获取floor组建的数据
export const reqFloorList= ()=>mockRequest.get('/floor');

2、到store仓库文件夹中的home.js

import {reqCategoryList,reqGetBannerList,reqFloorList} from '@/api';

3、写action,用户处理派发action的地方

//    获取floor的数据
    async getFloorList({commit}){
       let result = await reqFloorList();
       if (result.code==200){
           //提交mutation
           commit('GETFLOORLIST',result.data);
       }
    }

4、到mutation,修改state的地方

GETFLOORLIST(state,floorList){
    state.floorList = floorList;
}

5、state存储数据的地方:数据格式取决于服务器返回的数据

floorList:[]

4.1 getFloorList这个action在哪里触发

由于floor组件复用,而且mock中给的数据有两组。所以触发就不能单独在floor组件内部写,在里面写没办法v-for遍历出两个floor组件

所以在home.vue路由中发

<Floor v-for="(floor,index) in "floorList" :key="floor.id"/>

1、在

posted @ 2022-09-27 16:40  孤舟蓑衣客  阅读(11)  评论(0)    收藏  举报