当前位置:   article > 正文

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

尚硅谷尚品汇

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

以下是之前的测试数据

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

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

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

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

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

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

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

  1.    //过渡动画样式---name属性
  2.    //开始状态(进入)
  3.    .sort-enter {
  4.      height: 0px;
  5.   }
  6.    //结束状态(进入0
  7.    .sort-enter-to {
  8.      height: 461px;
  9.   }
  10.    //定义动画时间速率等(进入的过程)
  11.    .sort-enter-active {
  12.      transition: all 0.5s linear;
  13.   }

2 三级列表的优化

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

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

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

3 合并paramsquery参数

TypeNav

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

Header中的搜索

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

这边这样写是有问题的,比如在主页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文件在入口文件中引入(至少需要执行一次。才能模拟数据)

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

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

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

5 ListContainer组件开发重点

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

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

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

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

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

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

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

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

复习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存在了】

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

6 开发Floor组件

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

  • 仓库存储数据三连环

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

  1. //1 写API
  2. //获取floor数据
  3. export const reqGetFloorList = () => mockRequests({ url: '/floor', method: 'get' })
  4. //2 仓库三连环
  5. const state = {
  6.    floorList: []
  7. }
  8. const action = {
  9.    // 获取floor数据,commit意思是将来数据要提交给mutations
  10.    async getFloorList(context) {
  11.        let result = await reqGetFloorList();
  12.        if (result.code == 200) {
  13.            //提交mutation
  14.            context.commit('GETFLOORLIST', result.data)
  15.       }
  16.   }
  17. }
  18. const mutation = {
  19.    GETFLOORLIST(state, floorList) {
  20.   state.floorList = floorList
  21.   }
  22. }

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

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

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

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

组件间的通信方式有哪些

  • props:父子间

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

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

  • pubsub-js:几乎不用 全能

  • 插槽(三种)

  • vuex

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

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

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

一个注意点:

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

7 优化一下主页结构

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

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

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

  1.  watch: {
  2.    list: {
  3.      //父亲传过来的数据没有变化,监听不到,所以用immediate上来就监听一次
  4.      immediate: true,
  5.      handler() {
  6.        //只能监听数据,v-for动态渲染结构还无法确定,所以还需要nextTick
  7.        this.$nextTick(() => {
  8.          new Swiper(".swiper-container", {
  9.            loop: true, // 循环模式选项
  10.            // 如果需要分页器
  11.            pagination: {
  12.              el: ".swiper-pagination",
  13.              clickable: true,
  14.           },
  15.            // 如果需要前进后退按钮
  16.            navigation: {
  17.              nextEl: ".swiper-button-next",
  18.              prevEl: ".swiper-button-prev",
  19.           },
  20.         });
  21.       });
  22.     },
  23.   },
  24. },

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

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

  1. Carousel组件
  2. <template>
  3.  <div class="swiper-container" ref="floor1Swiper">
  4.    <div class="swiper-wrapper">
  5.      <div
  6.        class="swiper-slide"
  7.        v-for="carousel in list"//因为现在是这样传:list="list.carouselList"所以不用list.carouselList直接list
  8.       :key="carousel.id"
  9.      >
  10.        <img :src="carousel.imgUrl" />
  11.      </div>
  12.    </div>
  13.    <!-- 如果需要分页器 -->
  14.    <div class="swiper-pagination"></div>
  15.    <!-- 如果需要导航按钮 -->
  16.    <div class="swiper-button-prev"></div>
  17.    <div class="swiper-button-next"></div>
  18.  </div>
  19. </template>
  20. <script>
  21. import Swiper from "swiper";
  22. export default {
  23.  props: ["list"],
  24.  watch: {
  25.    list: {
  26.      //父亲传过来的数据没有变化,监听不到,所以用immediate上来就监听一次
  27.      immediate: true,
  28.      handler() {
  29.        //只能监听数据,v-for动态渲染结构还无法确定,所以还需要nextTick
  30.        this.$nextTick(() => {
  31.          new Swiper(this.$refs.floor1Swiper, {
  32.            loop: true, // 循环模式选项
  33.            // 如果需要分页器
  34.            pagination: {
  35.              el: ".swiper-pagination",
  36.              clickable: true,
  37.           },
  38.            // 如果需要前进后退按钮
  39.            navigation: {
  40.              nextEl: ".swiper-button-next",
  41.              prevEl: ".swiper-button-prev",
  42.           },
  43.         });
  44.       });
  45.     },
  46.   },
  47. },
  48. };
  49. </script>

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

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

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

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

8 Search模块的开发

模块四部曲

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

  • 发请求(API

  • vuex仓库(三连环)

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

发送带参请求

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

search小仓库中:

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

search组件中捞数据

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

展示数据

  1. <li class="yui3-u-1-5" v-for="good in goodsList" :key="good.id">
  2.  <div class="list-wrap">
  3.    <div class="p-img">
  4.      <a href="item.html" target="_blank"
  5.        ><img :src="good.defaultImg"
  6.      /></a>
  7.    </div>
  8.    <div class="price">
  9.      <strong>
  10.        <em>¥ </em>
  11.        <i>{{ good.price }}.00</i>
  12.      </strong>
  13.    </div>
  14.    <div class="attr">
  15.      <a
  16.        target="_blank"
  17.        href="item.html"
  18.        title="促销信息,下单即赠送三个月CIBN视频会员卡!【小米电视新品4A 58 火爆预约中】"
  19.        >{{ good.title }}</a
  20.      >
  21.    </div>
  22.    <div class="commit">
  23.      <i class="command">已有<span>2000</span>人评价</i>
  24.    </div>
  25.    <div class="operate">
  26.      <a
  27.        href="success-cart.html"
  28.        target="_blank"
  29.        class="sui-btn btn-bordered btn-danger"
  30.        >加入购物车</a
  31.      >
  32.      <a href="javascript:void(0);" class="sui-btn btn-bordered"
  33.        >收藏</a
  34.      >
  35.    </div>
  36.  </div>
  37. </li>

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

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

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

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

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

修改好参数

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

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

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

今日陷阱:

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/69896
推荐阅读
相关标签
  

闽ICP备14008679号