Vue2项目实战--b站尚硅谷--尚品汇项目--详细笔记--day04

1 search模块中的TypeNav(过渡动画效果)

以下是之前的测试数据

  <div>
    <h1>params参数{
   
   { $route.params.keyword }}=========={
   
   { keyword }}</h1>
    <h1>query参数{
   
   { $route.query.k }}</h1>
  </div>
export default {
  name: "SearchYu",
  //路由传递 props 参数 要接受一下   有了props 上面模板就可以直接写了
  props: ["keyword"],
};

一个功能在一些组件中显示,在一些组件中隐藏,那么就可以考虑用路由路径来判断,或者在配置路由的时候加上元信息来判断(如Footer组件的显示与隐藏,或者TypeNav的显示与隐藏)

控制分类表显示与隐藏(home显示,其他隐藏),可以用一个响应式数据配合v-show来,但是注意返回home的时候需要显示,想到TypeNav的生命周期,每跳转一次路由TypeNav就会被挂载一次,所以在挂载完毕后判断当前路由是不是home,是的话显示,否则隐藏

标签中绑定事件:@mouseleave="leaveIndex" @mouseenter="enterShow"

//一级分类鼠标移除的事件回调
leaveIndex() {
    this.currentInedx = -1;
    //列表隐藏 需要加个判断
    if (this.$route.path != "/home") {
        this.show = false;
    }
},
        
//当鼠标移入全部商品分类的时候,展示商品分类列表
//这里也可以加判断
enterShow() {
    this.show = true;
},

注意:过渡动画的前提是组件或元素务必要有 v-ifv-show 指令才可以

<transition name="sort">...</transition>

    //过渡动画样式---name属性
    //开始状态(进入)
    .sort-enter {
      height: 0px;
    }
    //结束状态(进入0
    .sort-enter-to {
      height: 461px;
    }
    //定义动画时间速率等(进入的过程)
    .sort-enter-active {
      transition: all 0.5s linear;
    }

2 三级列表的优化

homesearch路由之间进行跳转的时候,会频繁发送请求,因为都用到了TypeNav组件 this.$store.dispatch("categoryList");

所以可以放到只执行一次的地方:App(根组件)的mounted(){}中----这样其他组件用数据的时候,仓库早已经有了

放在main.js中不可以,因为this不是同一个东西,main.js不是组件,我要的this是组件的this,组件身上才有$store属性,才可以this.store

3 合并paramsquery参数

TypeNav

        //判断,如果路由跳转时有params参数,一并带过去
        if (this.$route.params) {
          location.params = this.$route.params;
          //整理好参数
          location.query = query;
          //路由跳转
          this.$router.push(location);
        }

Header中的搜索

      //有query参数也传过去
      if (this.$route.query) {
        let location = { name: "search", params: { keyword: this.keyword } };
        location.query = this.$route.query;
        this.$router.push(location);
      }

这边这样写是有问题的,比如在主页home下,都还没点击呢。接下来点击三级列表,那要进入TypeNav中的if判断,此时没有params参数,为假,怎么发送push呢(这里注意为什么会成功跳转呢,因为if的空对象判断是true!!!!{}为true!!!)。一个办法是把push写在if外面,此时需要var声明location

4 开发Home首页中的ListContainer组件与Floor组件

服务器返回的只有分类菜单数据,对于ListContaineFloor组件没有提供数据

mock数据(模拟):想要使用模拟数据(mock),就需要安装一个插件 npm i mockjs

浏览器会拦截ajax请求,不会向服务器发送请求,自己随机生成数据,自己在前台玩

使用步骤

  • 在项目的src文件夹中创建mock文件夹

  • 准备JSON数据(在项目文档中提供了模拟数据),在mock文件夹中创建相应的JSON文件 注意需要格式化一下,有空格无法运行

  • mock需要的图片资源放置到public文件夹中,新建imagespublic文件夹在打包的时候,会把相应的资源原封不动的打包到dist文件夹中

  • 创建mockServe.js文件,通过mockjs插件实现模拟数据

webpack默认对外暴露的:图片资源,json数据格式

  • mockServer.js文件在入口文件中引入(至少需要执行一次。才能模拟数据)

mockServe中
//引入mockjs模块
import Mock from 'mockjs'
//把json数据格式引入---直接引入,默认对外暴露
import banner from './banner.json'
import floor from './floor.json'
​
//mock数据
//mock()的第一个参数:请求地址,第二个参数:请求数据
Mock.mock("/mock/banner", { code: 200, data: banner })//模拟首页轮播图的数据
Mock.mock("/mock/floor", { code: 200, data: floor })//模拟首页楼层的数据
main.js中
//引入mock数据
import '@/mock/mockServe'

OK,现在静态组件准备好了,mock数据准备好了,接下来向服务器发请求了获取服务器数据,存储到Vuex中,然后展示数据

注意mockjs发的请求会被浏览器拦截,但是用的时候就当作服务器返回的数据

5 ListContainer组件开发重点

首先,我们要开始发请求,但是之前封装的request是向真实服务器发请求的,以“/api”开头;但是我们现在要向mock发请求,是以“/mock”开头的,所以将request.js复制一份成mockRequest.js,同时修改baseURL

mockRequest.js中
baseURL: "/mock",
//开头跟这个对应:mock()的第一个参数:请求地址,第二个参数:请求数据
Mock.mock("/mock/banner", { code: 200, data: banner })
Mock.mock("/mock/floor", { code: 200, data: floor })
同时注意:
mockRequest中对外暴露的变量名修改一下 :mockRequests
请求拦截器和响应拦截器也要修改:mockRequests

现在需要在api文件index.js中,把发请求的函数封装好

api的index.js接口文件中
import mockRequests from './mockRequest'
//获取banner(首页轮播图接口)
export const reqGetBannerList = () => mockRequests({ url: '/banner', method: 'get', });

接下来发请求,数据放仓库---组件加载完毕的时候发请求(mounted)

轮播图组件中中
mounted() {
  //派发action:通过Vuex发起ajax请求,将数据存储在仓库中
  this.$store.dispatch("home/getBannerList");
},
在home小仓库中配置对应函数---仓库三连环
第一步actions
async getBannerList(context) {
    const result = await reqGetBannerList();
    //成功返回的话我们要修改仓库中的数据了
    if (result.code == 200) {
        context.commit('GETBANNERLIST', result.data)
    }
}
第二步mutations
GETBANNERLIST(state, bannerList) {
    //修改state中的categoryList---事先准备好空的categoryList
    state.bannerList = bannerList
},
第三步state中配置对应数据类型的初始值
bannerList: []
//OK,到此为止数据已经存储在仓库中了,接下来就是组件拿数据进行展示

ListContainer组件中---组件从仓库中拿数据

ListContainer组件中
import { mapState } from "vuex";
我在home小仓库中开启了命名空间
...mapState("home", ["bannerList"]),
//到此位置,ListContainer组件拿到了仓库中的数据了
接下来在组件中进行展示即可

复习Swiper

安装Swiper插件:npm i swiper@5

swiper三步骤:引包(JSCSS),页面结构,创建Swiper实例(放在mounted当中不行,因为v-for遍历的时候,结构还没完全--因为是ajax异步请求,派发action--action向服务器请求数据--new Swiper实例(问题出在这,数据还没回来,v-for还未执行,结构当然没有了)--mutation修改仓库数据,异步。放updated中也没必要,因为将来如果还有更新的数据,每次都要new Swiper实例,没必要)

方案一:mounted中设置延迟器,new Swiper放在延迟器里面,不推荐

方案二:用监听watch+nextTick解决。监听bannerList数据的变化

$nextTick:将回调延迟到下次DOM更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。

$nextTick:可以保证页面的结构一定是有的,再new swiper实例。经常和很多插件一起使用【都需要DOM存在了】

swiper引包在组件引入即可(JS)
swiper引入样式(CSS)的时候,可以在每个组件中引入,但是考虑岛多个组件都会使用,所以在main.js中引入
ListContainer组件中:
//引包
import Swiper from "swiper";
//引入样式---在main.js中引入
main.js文件中
import 'swiper/css/swiper.css'
ListContainer组件中:
准备好结构和样式
<div
  class="swiper-slide"
  v-for="carousel in bannerList"//展示数据
  :key="carousel.id"
>
  <img :src="carousel.imgUrl" />//动态绑定
</div>
​
引入swiper动态操作的JS
在下一轮更新中创建实例this.$nextTick()
$nextTick:将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。
watch: {
  //监听bannerList数据的变化---有空数组变成数组中有四个对象
  bannerList: {
    handler(newValue, oldValue) {
      // console.log(newValue, oldValue);
      //通过watch监听bannerList属性的变化
      //如果handler回调执行,说明组件实例身上这个属性的属性值已经有了---只能保证数据已经有了,结构不一定
      //v-for执行完毕了结构才有
      this.$nextTick(() => {
        //当下一次DOM更新结束后执行回调
        var mySwiper = new Swiper(".swiper-container", {
          loop: true, // 循环模式选项
​
          // 如果需要分页器
          pagination: {
            el: ".swiper-pagination",
            //点击小球切换图片
            clickable: true,
          },
​
          // 如果需要前进后退按钮
          navigation: {
            nextEl: ".swiper-button-next",
            prevEl: ".swiper-button-prev",
          },
        });
      });
    },
  },
},
​
//注意:Swiper函数的第一个参数也可以是DOM元素
直接操作DOM不太好,可以用ref给节点打标签,然后
this.$refs.mySwiper获取节点元素

6 开发Floor组件

  • 静态页面写完,发请求-----写API

  • 仓库存储数据三连环

  • 组件捞数据-----展示

//1 写API
//获取floor数据
export const reqGetFloorList = () => mockRequests({ url: '/floor', method: 'get' })
//2 仓库三连环
const state = {
    floorList: []
}
const action = {
    // 获取floor数据,commit意思是将来数据要提交给mutations
    async getFloorList(context) {
        let result = await reqGetFloorList();
        if (result.code == 200) {
            //提交mutation
            context.commit('GETFLOORLIST', result.data)
        }
    }
}
const mutation = {
    GETFLOORLIST(state, floorList) {
        state.floorList = floorList
    }
}

注意:在Home组件中去派发请求获取数据,Home组件拿到数据,而不是在Floor组件中。因为我们要遍历Floor组件

//getFloorList这个action在哪里触发?需要在Home路由组件中触发。不能在floor这个组件,因为我们需要v-for遍历floor组件
//3 路由组件捞数据 注意是哪个
//在floor的父亲Home路由组件上捞 派发action
mounted() {
    //在派发action,获取floor组件的数据
    this.$store.dispatch("getFloorList");
},
//有数据之后 让Home组件拿到数据
import { mapState } from "vuex";
computed: {
    ...mapState({
        floorList: (state) => state.home.floorList,
    }),
},
//Home组件数据已经有了,里面就不用写两个<FloorYu />,可以用v-for遍历了
<FloorYu v-for="floor in floorList" :key="floor.id"/>

v-for也可以在自定义标签中使用

到此为止,父组件Home拿到数据,在子组件FloorYuv-for。接下来要给子组件把数据送过去,涉及父子组件中的数据通信

组件间的通信方式有哪些

  • props:父子间

  • 自定义事件:@on @emit 子给父

  • 全局事件总线:$bus 全能

  • pubsub-js:几乎不用 全能

  • 插槽(三种)

  • vuex

我们这里Home组件给Floor组件传数据 用props

//Home组件中 用:list   传过去(注意 写floor可能报错
<FloorYu v-for="floor in floorList" :key="floor.id" :list="floor" />
//Floor组件用props接受
props: ["list"],

接下来子组件拿到数据之后,动态展示数据(在FloorYu组件的标签中,将死的数据,用插值语法填入动态数据

一个注意点:

  mounted() {
    //上一次ListContainer写的时候,放在mounted中不可以,这次为什么可以
    //第一次写轮播图,是在当前组件内部发送请求的,动态渲染解构【前台至少服务器数据需要回来】,因此不行
    //而这次没有在Floor内部发请求,数据是父组件Home给的,在挂载完毕之前数据和结构都完成了
    new Swiper(".swiper-container", {
      loop: true, // 循环模式选项
​
      // 如果需要分页器
      pagination: {
        el: ".swiper-pagination",
        clickable: true,
      },
​
      // 如果需要前进后退按钮
      navigation: {
        nextEl: ".swiper-button-next",
        prevEl: ".swiper-button-prev",
      },
    });
  },

7 优化一下主页结构

轮播图在ListContainerFloor都有用到,所以可以封装成一个全局组件使用

要保证大概一致,结构样式交互

所以Floor里面也改用监听来写:

  watch: {
    list: {
      //父亲传过来的数据没有变化,监听不到,所以用immediate上来就监听一次
      immediate: true,
      handler() {
        //只能监听数据,v-for动态渲染结构还无法确定,所以还需要nextTick
        this.$nextTick(() => {
          new Swiper(".swiper-container", {
            loop: true, // 循环模式选项
​
            // 如果需要分页器
            pagination: {
              el: ".swiper-pagination",
              clickable: true,
            },
​
            // 如果需要前进后退按钮
            navigation: {
              nextEl: ".swiper-button-next",
              prevEl: ".swiper-button-prev",
            },
          });
        });
      },
    },
  },

公用的组件和非路由组件都放在components文件夹中

首先,在components文件夹下创建Carousel文件---index.vue。将轮播图结构剪切过去,将轮播图JSwatch剪切过去

Carousel组件
<template>
  <div class="swiper-container" ref="floor1Swiper">
    <div class="swiper-wrapper">
      <div
        class="swiper-slide"
        v-for="carousel in list"//因为现在是这样传:list="list.carouselList" ,所以不用list.carouselList,直接list
        :key="carousel.id"
      >
        <img :src="carousel.imgUrl" />
      </div>
    </div>
    <!-- 如果需要分页器 -->
    <div class="swiper-pagination"></div>
​
    <!-- 如果需要导航按钮 -->
    <div class="swiper-button-prev"></div>
    <div class="swiper-button-next"></div>
  </div>
</template>
​
<script>
import Swiper from "swiper";
export default {
  props: ["list"],
  watch: {
    list: {
      //父亲传过来的数据没有变化,监听不到,所以用immediate上来就监听一次
      immediate: true,
      handler() {
        //只能监听数据,v-for动态渲染结构还无法确定,所以还需要nextTick
        this.$nextTick(() => {
          new Swiper(this.$refs.floor1Swiper, {
            loop: true, // 循环模式选项
​
            // 如果需要分页器
            pagination: {
              el: ".swiper-pagination",
              clickable: true,
            },
​
            // 如果需要前进后退按钮
            navigation: {
              nextEl: ".swiper-button-next",
              prevEl: ".swiper-button-prev",
            },
          });
        });
      },
    },
  },
};
</script>

然后,在入口文件main.js中全局注册

//轮播图组件----注册全局组件
import Carousel from '@/components/Carousel'
Vue.component('Carousel', Carousel)

然后,在需要的组件中使用Carousel组件---<Carousel :list="list.carouselList" />(现在给子组件传递数据---Floor组件给Carousel组件传递props方法)

同理,将ListContainer中的轮播图结构和JS干掉,swiper也不用引入了---直接:<Carousel :list="bannerList" />

8 Search模块的开发

模块四部曲

  • 先写静态页面+静态组件拆分出来

  • 发请求(API

  • vuex仓库(三连环)

  • 组件获取仓库数据,动态展示数据

发送带参请求

//当前这个函数需要外部传递参数吗  要
//给服务器传递的params参数,至少是一个空对象
export const reqGetSearchInfo = (params) => requests({ url: '/list', method: 'post', data: params });

search小仓库中:

首先引入ajax函数
import { reqGetSearchInfo } from '@/api'
其次书写仓库三连环---注意searchList的类型,可以先在search组件中派发action回来看看数据再写
state: {
    //类型不能瞎写,根据返回来
    searchList: {}
},
actions: {
    //通过API里面的接口函数调用,向服务器发请求,获取search模块的数据
    async getSearchList(context, params = {}) {
        //reqGetSearchInfo这个函数调用时,至少传递一个空对象
        //params形参,是当用户派发action的时候,第二个参数传递过来的,至少是一个空对象
        const result = await reqGetSearchInfo(params);
        //成功返回的话我们要修改仓库中的数据了
        if (result.code == 200) {
            context.commit('GETSEARCHLIST', result.data)
        }
    },
},
mutations: {
    GETSEARCHLIST(state, searchList) {
        state.searchList = searchList
    },
},
    //OK到此为止仓库里面有数据了,正常来说我们接下来回到组件中捞数据,展示数据就可以
//但是在这里,我们可以现在仓库中整理好,再捞数据----getter
getters: {
    goodsList(state) {
        return state.searchList.goodsList || []
    },
    tradeMarkList(state) {
        return state.searchList.trademarkList || []
    },
    attrsList(state) {
        return state.searchList.attrsList || []
    },
}

search组件中捞数据

//开启了命名空间,需要加上'search'
...mapGetters("search", ["goodsList"]),

展示数据

<li class="yui3-u-1-5" v-for="good in goodsList" :key="good.id">
  <div class="list-wrap">
    <div class="p-img">
      <a href="item.html" target="_blank"
        ><img :src="good.defaultImg"
      /></a>
    </div>
    <div class="price">
      <strong>
        <em>¥ </em>
        <i>{
   
   { good.price }}.00</i>
      </strong>
    </div>
    <div class="attr">
      <a
        target="_blank"
        href="item.html"
        title="促销信息,下单即赠送三个月CIBN视频会员卡!【小米电视新品4A 58 火爆预约中】"
        >{
   
   { good.title }}</a
      >
    </div>
    <div class="commit">
      <i class="command">已有<span>2000</span>人评价</i>
    </div>
    <div class="operate">
      <a
        href="success-cart.html"
        target="_blank"
        class="sui-btn btn-bordered btn-danger"
        >加入购物车</a
      >
      <a href="javascript:void(0);" class="sui-btn btn-bordered"
        >收藏</a
      >
    </div>
  </div>
</li>

注意:发请求写在search组件的mounted中只能发送一次,而search组件要随着搜索内容的不同,或者用户点击列表的不同,带上不同的参数向服务器发请求获取数据

可以把发请求封装成一个函数getData()

getData() {
  this.$store.dispatch("search/getSearchList", this.searchParams);
},

同时,我们把要带过去的参数不能写死为空对象,可以设置一个响应式数据searchParams:{},要带哪些参数具体看API接口

//带给服务器的参数
searchParams: {
  //注意这边的参数初始值,是用户点击或输入的
  category1Id: "",
  category2Id: "",
  category3Id: "",
  categoryName: "",
  keyword: "",
  order: "",
  pageNo: 1,
  pageSize: 3,
  props: [],
  trademark: "",
}
//现在都是初始值,将来要修改他们的值,带给服务器,返回不同数据进行展示

修改好参数

//当组件挂载完毕之前,整理好参数之后再发请求
beforeMount() {
  //复杂写法
  // this.searchParams.category1Id = this.$route.query.category1Id;
  // this.searchParams.category2Id = this.$route.query.category2Id;
  // this.searchParams.category3Id = this.$route.query.category3Id;
  // this.searchParams.categoryName = this.$route.query.categoryName;
  // this.searchParams.keyword = this.$route.params.keyword;
  //Object.assign:ES6新增语法,合并对象
  Object.assign(this.searchParams, this.$route.query, this.$route.params);
},

search的子组件拿数据,展示数据

import { mapGetters } from "vuex";
...mapGetters("search", ["tradeMarkList", "attrsList"]),
//展示数据即可

今日陷阱:

  • bannerList的属性时,把imgUrl写成imgURL,导致home主页轮播图丢失

  • 记得修改swiper的高和宽,适应轮播图大小

  • 注意轮播图的使用三步骤,尤其是数据模板的和实例的先后关系

猜你喜欢

转载自blog.csdn.net/m0_55644132/article/details/127507809#comments_25952094