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

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

以下是之前的测试数据

  
   

params参数{{ $route.params.keyword }}=========={{ keyword }}

   

query参数{{ $route.query.k }}

 
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 指令才可以

...

    //过渡动画样式---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组件中:
准备好结构和样式
 //动态绑定
​ 引入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组件数据已经有了,里面就不用写两个,可以用v-for遍历了

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

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

组件间的通信方式有哪些

  • props:父子间

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

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

  • pubsub-js:几乎不用 全能

  • 插槽(三种)

  • vuex

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

//Home组件中 用: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组件

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

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

然后,在需要的组件中使用Carousel组件---(现在给子组件传递数据---Floor组件给Carousel组件传递props方法)

同理,将ListContainer中的轮播图结构和JS干掉,swiper也不用引入了---直接:

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"]),

展示数据

  •  
       
             
       
                 ¥        {{ good.price }}.00          
       
         {{ good.title }}    
       
         已有2000人评价    
       
         加入购物车      收藏    
     
  • 注意:发请求写在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的高和宽,适应轮播图大小

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

    你可能感兴趣的:(前端项目,css,html,javascript,前端框架,ajax)