Vue2电商前台项目(二):完成Home首页模块业务

目录

一、项目开发的步骤

二、Home首页拆分静态组件

1.TypeNav三级联动的完成

2.完成其余静态组件

3.POSTMAN测试接口

三、请求服务器数据的准备工作

1.axios二次封装

2.api接口统一管理

3.nprogress进度条的使用

四、Vuex模块化开发

五、TypeNav导航三级联动

1.三级联动展示数据

(1)组件挂载完毕后dispatch给Vuex

(2)去home仓库请求数据

(3)TypeNav接收数据

(4)v-for去掉多余a标签

2.一级分类动态展示背景颜色

(1)采用样式完成(hover)

(2)通过js完成

3.JS控制二、三级数据显示和隐藏

4.三级联动的防抖与节流

(1)防抖和节流是什么(面试频率高)

(2)三级联动导航节流

5.三级联动路由跳转与传参

(1)需求分析

(2)编程式导航+事件委派

6.search模块中三级联动的显示与隐藏

(1)实现显示与隐藏

(2)添加过渡动画

7.解决三级导航ajax请求重复发送的bug

8.合并params与query参数

六、开发Home首页中的ListContainer、Floor组件

1.mock搭建模拟数据

2.mock虚拟数据的ajax请求

3.制作Banner轮播图的数据

4.制作ListContainer轮播图

(1)引入相应的包(js和css)

(2)搭建轮播图页面结构

(3)给轮播图添加动态效果

第一种方法:setTimeout定时器(不好)

第二种方法:放在update里(不好)

第三种方法:watch(完美!)

nextTick用法:

5.开发floor组件

(1)从mock拿数据

(2)Home中的数据传给Floor

复习一下组件通信的方式有哪些?面试频率极高

(3)数据渲染到Floor页面

(4)把轮播图封装为全局组件


一、项目开发的步骤

1、书写静态页面(HTML,CSS)
2、拆分组件
3、获取服务器的数据动态展示
4、完成相应的动态业务逻辑

经过分析之后,Home首页可以拆分为7个组件:TypeNav三级联动导航,ListContainer,Recommend,Rank,Like,Floor,Brand。主要得看你静态页面把谁谁写在一个结构里面了,组件就得在一个里面。

二、Home首页拆分静态组件

1.TypeNav三级联动的完成

如果有哪个组件在项目中频繁使用(三级联动在Home、Search、Detail都用到了),就把它注册成全局组件。

好处:只需要注册一次可以在项目的任意地方使用。

首先在home下新建一个三级联动组件的文件夹TypeNav,底下有一个文件叫index.vue,然后把它注册为全局组件,回到入口文件main.js,需要使用到Vue.component

//三级联动的组件——全局组件
import TypeNav from '@/pages/Home/TypeNav'
//第一个参数是全局组件的名字,第二个参数是:哪一个组件
Vue.component(TypeNav.name,TypeNav)

现在回到home组件里就可以使用TypeNav了:

<!-- 三级联动全局组件 ,它已经注册为全局组件了,不需要再引入-->
    <TypeNav/>

2.完成其余静态组件

完成轮播图和右边快报的部分

拆分组件就三步:结构、样式、图片资源

3.POSTMAN测试接口

刚刚经过postman工具测试,接口是没有问题的
如果服务器返回的数据code字段200,代表服务器返回数据成功
整个项目,接口前缀都有/api字样

三、请求服务器数据的准备工作

1.axios二次封装

向服务器发请求:XMLHttpRequest、fetch、JQ、axios

为什么需要进行二次封装axios?
请求拦截器、响应拦截器:请求拦截器,可以在发请求之前可以处理一些业务、响应拦截器,当服务器数据返回以后,可以处理一些事情

首先安装axios :npm install axios

项目当中通常放API文件夹用于放axios,src/api/request.js:

//对于axios进行二次封装
import axios from 'axios'

//利用axios对象的方法create,去创建一个axios实例
//request就是axios,只不过配置一下

const requests = axios.create({
    //配置对象
    //基础路径
    //baseURL: '/api',
    //代表请求时间超时,超过五秒还没发回来说明请求失败
    timeout: 5000,
})
//create方法里面可以写对象

//请求拦截器
requests.interceptors.request.use((config) => {
    //config是一个配置对象,里面有一个属性很重要:header请求头
    return config
})

//响应拦截器,有成功回调和失败回调
requests.interceptors.response.use((res)=>{
//成功的回调函数:服务器相应数据回来以后,响应拦截器可以检测到
return res.data
},(err)=>{
//响应失败
return Promise.reject(new Error('false'))
})
export default requests

2.api接口统一管理

项目很小:完全可以在组件的生命周期函数中发请求,在mounted或者created里发请求,存储在data当中。

项目大的话,专门建立一个index.js进行统一管理

//当前这个模块,所有API接口进行统一的管理
//发请求用到axios,引入进来
import requests from "./request";

//三级联动的接口
 export const reqcategoryList=()=>{
    //axios发请求返回promise对象
    return requests({url:'http://gmall-h5-api.atguigu.cn/api/product/getBaseCategoryList',method:'get'})
 }

main.js调用

import {reqcategoryList} from '@/api'
reqcategoryList()

但是请求错误,因为出现了跨域问题,我们所在的是本地服务器,请求的接口不在

解决跨域问题:JSONP、CROS、代理

这里我们选择代理,在vue.config.js:

//代理跨域,第三方
  devServer: {
    proxy: {
      '/api': {
        target: 'http://gmall-h5-api.atguigu.cn/api/product/getBaseCategoryList',
        //pathRewrite: { '^/api': '' },
      },
    },
  }

注意:数据请求需要服务器+接口 不要只是访问服务器这样是拿不到数据的,
比如 http://gmall-h5-api.atguigu.cn(这是服务器) /api/product/getBaseCategoryList(这是接口)把接口放在服务器后面就可以
例如:http://gmall-h5-api.atguigu.cn/api/product/getBaseCategoryList 不要只是访问服务器

3.nprogress进度条的使用

安装插件:npm install nprogress

start:进度条开始

done:进度条结束

//引入进度条
import nprogress from 'nprogress'
//引入进度条的样式
import 'nprogress/nprogress.css'
//请求拦截器
requests.interceptors.request.use((config) => {
    //进度条开始动
    nprogress.start()
    //config是一个配置对象,里面有一个属性很重要:header请求头
    return config
})

//响应拦截器,有成功回调和失败回调
requests.interceptors.response.use((res) => {
    //进度条结束
    nprogress.done()
    //成功的回调函数:服务器相应数据回来以后,响应拦截器可以检测到
    return res.data
}, (err) => {
    //响应失败
    //return Promise.reject(new Error('false'))
    return err.message
})
export default requests

四、Vuex模块化开发

vuex是官方提供一个插件,状态管理库,集中式管理项目中组件共用的数据。
切记,并不是全部项目都需要Vuex,如果项目很小,完全不需要Vuex,如果项目很大,组件很多、数据很多,数据维护很费劲,Vuex几个核心概念:

state:仓库存储数据的地方
mutations:唯一修改state手段
actions:处理action,可以书写自己的业务逻辑,也可以处理异步
getters:计算属性,用于简化仓库数据,让组件获取仓库的数据更加方便
modules:模块式开发

安装vuex:npm i vuex@3

注意vue2的一定不要下错!!!!!

vuex是一个对象,store是它的一个方法,而这个方法是一个构造函数,可以初始化vue仓库

新建一个src下的store文件/index.js:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

//对外暴露store类的一个实例
export default new Vuex.Store({
    state:{},
    mutations:{},
    actions:{},
    getters:{}
})

然后到入口文件去注册一下:

//引入仓库
import store from './store' 

new Vue({
  render: h => h(App),
  router,
  //注册仓库:组件实例的身上会多一个$store的属性
  store
  //KV一致省略V
  //注册路由信息:当这里书写router的时候,组件身上都拥有$route,$router
}).$mount('#app')

如果项目过大,数据过多,可以让vuex实现模块式开发,大仓库拆分成小仓库,每一个小仓库存储相应模块的数据

比如我们现在创建home、search的小仓库,分别在store下创建两个文件夹为home、search

然后他俩下面再创建index.js

//search模块下的小仓库
const state={}
const mutations={}
const actions={}
const getters={}
export default {
    state,
    mutations,
    actions,
    getters
}

然后把两个小仓库合并到大仓库中去

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//引入小仓库
import home from './home'
import search  from './search'
//对外暴露store类的一个实例
export default new Vuex.Store({
    //实现Vuex仓库模块化开发存储数据
    modules:{
        home,
        search
    }
})

历尽千辛万苦终于知道为啥我没有vuex的数据了,下载的时候下成了最新版的vuex,得先卸载了最新的再下,卸载:npm uninstall vuex

五、TypeNav导航三级联动

1.三级联动展示数据

一个小更改,之前的三级联动我们写在了home下,以后像这种不会更改的组件都写在components里

(1)组件挂载完毕后dispatch给Vuex

//组件挂载完毕,可以向服务器发请求
  mounted(){
    this.$store.dispatch('categoryList')
  },

(2)去home仓库请求数据

code号为200时,就把数据给过去

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();
        if (result.code == 200) {
            commit("CATEGORYLIST", result.data)
        }
    }
};

(3)TypeNav接收数据

import{mapState} from 'vuex'
、、、
computed:{
    ...mapState({
      //右侧需要的是一个函数,当使用这个计算属性的时候,右侧函数会立即执行一次
      //注入一个参数state,其实即为大仓库中的数据
      categoryList:(state)=>{
        return state.home.categoryList
      }
    })
  }

(4)v-for去掉多余a标签

一级分类叫item,二级分类叫subitem,是一级分类中的categoryChild,还有三级分类,用em做的

<div class="item" v-for="c1 in categoryList" :key="c1.categoryId">
            <h3>
              <a href="">{{c1.categoryName}}</a>
            </h3>
            <div class="item-list clearfix">
              <div class="subitem" v-for="c2 in c1.categoryChild" :key="c2.categoryId">
                <dl class="fore">
                  <dt>
                    <a href="">{{c2.categoryName}}</a>
                  </dt>
                  <dd>
                    <em v-for="c3 in c2.categoryChild" :key="c3.categoryId">
                      <a href="">{{c3.categoryName}}</a>
                    </em>
                  </dd>
                </dl>
              </div>
            </div>
          </div>

2.一级分类动态展示背景颜色

(1)采用样式完成(hover)

         .item:hover{
                      background-color: skyblue;
                    }

(2)通过js完成

当用户鼠标移到哪个一级分类上就把它的索引值index给存起来,mouseenter事件,传参过来index,设置一个动态类名,谁被移上去谁就有那个类名

<div class="item" v-for="(c1,index) in categoryList" :key="c1.categoryId" :class="{cur:currentIndex==index}">
            <h3 @mouseenter="changeIndex(index)">
              <a href="">{{c1.categoryName}}</a>
            </h3>
data(){
    return {
      currentIndex:-1
      //用来记录鼠标在谁上,-1表示都不在
    }
  },
methods: {
    //鼠标进入修改index
    changeIndex(index){
      //index是鼠标在的一级分类
      this.currentIndex=index
    }
  },
.cur{
      background-color: skyblue;
     }

这样写实现了鼠标移上去背景颜色变蓝,但是鼠标移下来它还是蓝的(在鼠标最后待的h3)

所以还得有mouseleave事件,本来我还想着动颜色,结果发现直接index=-1更简单

<h3 @mouseenter="changeIndex(index)" @mouseleave="leaveIndex(index)">
、、、
leaveIndex(index){
      //鼠标移出的index=-1
      this.currentIndex=-1
    }

最后老师又更改了一下,当鼠标从第一个h3移到h2的时候蓝色不变,移出h2蓝色才消失,那么这个时候事件不能添加给h3了,用到了事件的委托,移出事件添加给父标签

<div @mouseleave="leaveIndex">
        <h2 class="all">全部商品分类</h2>
        <div class="sort">
          、、、、
              <h3 @mouseenter="changeIndex(index)">
                <a href="">{{ c1.categoryName }}</a>
              </h3>
              、、、

3.JS控制二、三级数据显示和隐藏

最开始是通过css样式display:none、block实现的

js实现:谁有背景颜色谁就有二三级分类

<!-- 二、三级分类 -->
<div class="item-list clearfix" :style="{display:currentIndex==index?'block':'none'}">

4.三级联动的防抖与节流

(1)防抖和节流是什么(面试频率高

卡顿现象:事件触发非常频繁,而且每一次的触发,回调函数都要去执行(如果时间很短,而回调函数内部有计算,那么很可能出现浏览器卡顿)

节流:在规定的间隔时间范围内不会重复触发回调,只有大于这个时间间隔才会触发回调,把频繁触发变为少量触发

防抖:前面的所有的触发都被取消,最后一次执行在规定的时间之后才会触发,也就是说如果连续快速的触发 只会执行一次

防抖:写一个简单的页面来模拟,用户在搜索input表框时,直至输入停止再过一秒之后ajax才会发请求。(强调时刻)

//防抖:前面的所有的触发都被取消,最后一次执行 在规定的时间之后才会触发,也就是说如果连续快速的触发 只会执行一次
let input = document.querySelector('input');
//文本发生变化立即执行
input.oninput=_.debounce(function(){
console.log('ajax发请求')
},1000);
//lodash插件:里面封装函数的防抖与节流的业务【闭包+延迟器】
//1:1odash函数库对外暴露_函数

lodash原生代码 得看

节流:多么频繁的点击都是五秒钟加一次(强调时间)

//计数器:在一秒以内,数字只能加上1
button.onclick =_.throttle(function(){
//节流:目前这个回调函数5S执行一次,
//加入这里面有很多的业务代码,是不是可以给浏览器很充裕的时间去解析
count++;
span.innerHTML = count
console.log('执行')
},5000);
//防抖:用户操作很频繁,但是只是执行一次
//节流:用户操作很频繁,但是把频繁的操作变为少量操作【可以给浏览器有充裕的时间解析代码】

(2)三级联动导航节流

在项目中使用一下节流技术,防止用户操作过快

项目中node_module里有lodash,不用再自己下载

//这种是把lodash全部功能引入过来了
//import _ from 'lodash'
//按需引入,默认暴露所以不用再加{}了
import throttle from 'lodash/throttle'
、、、
methods: {
    //鼠标进入修改index
    // changeIndex(index) {
    //   //index是鼠标在的一级分类
    //   this.currentIndex = index;
    // },
    changeIndex:throttle(function(index){
      this.currentIndex = index;
    },50),

注意throttle回调函数不要使用箭头函数,容易产生this指向问题

5.三级联动路由跳转与传参

(1)需求分析

三级联动用户可以点击的:一级分类、二级分类、三级分类,当你点击的时候Home模块跳转到Search模块,一级会把用户选中的产品(产品的名字、产品的ID)在路由跳转的时候,进行传递。

路由跳转的方式:声明式导航:router-link;编程式导航:push|replace
--如果使用声明式导航router-link,可以实现路由的跳转与传递参数,但是会出现卡顿现象。因为当服务器的数据返回之后,循环出很多的router-link组件【创建组件实例的】1000+创建组件实例的时候,一瞬间创建1000+很好内存的,因此出现了卡领现象。

--使用编程式导航的话,给a标签一个一个加@click方法,每个a都有属于自己的回调,一循环就有一千多个回调,这种写法也不太好,所以我们可以用时间委派,给这些a标签的父亲加@click方法,但是还是有一些问题,给父亲加,父亲下面的所以儿子包括a标签和不是a标签的(div、dt、dl)都有回调了。

(2)编程式导航+事件委派

怎么能确定点击的是a标签而且还是一级a标签呢?给所有的a标签添加一个自定义事件,点击的时候能传过来一个event

<h3 @mouseenter="changeIndex(index)">
   <a
      :data-categoryName="c1.categoryName"
      :data-category1Id="c1.category1Id"
   >{{ c1.categoryName }}</a >
</h3>
<a
    :data-categoryName="c2.categoryName"
    :data-category2Id="c2.category2Id"
 >{{ c2.categoryName }}</a>
<a
 :data-categoryName="c3.categoryName"
 :data-category3Id="c3.category3Id"
 >{{ c3.categoryName }}</a>

一二三级全加上自定义事件categoryName和category?Id,用dataset获取(用dataset获取过来的字母都是小写的)(对象用{}获取)

goSearch(event) {
      //那么我们怎么确定点击的是a标签呢
      //确定点击的是a标签怎么确定点击的是一级的a标签呢
      let element = event.target;
      let { categoryname, category1id, category2id, category3id } =
        element.dataset;
      //节点的dataset属性可以获取节点的自定义属性和属性值
      if (categoryname) {
        let location = { name: "search" };
        let query = { categoryName: categoryname };
        if (category1id) {
          query.category1Id = category1id;
        } else if (category2id) {
          query.category2Id = category2id;
        } else {
          query.category3Id = category3id;
        }
        //整理完参数
        location.query=query
        //路由跳转
        this.$router.push(location)
      }
    },
  },

有点头大,这里写完之后还出现了bug,跳转之后有数据但是链接没有search

在router中的path用?占位就解决了

path:'/search/:keyword?',

6.search模块中三级联动的显示与隐藏

(1)实现显示与隐藏

我们在search组件中直接写全局组件<typeNav/>,得到下图,但是菜单栏是不需要的,隐藏v-if或v-show,这个得回到typeNav去设置,改谁到谁里面设置

<div class="sort" v-show="show">
  data() {
    return {
      currentIndex: -1,
      //用来记录鼠标在谁上,-1表示都不在
      show:true
    };
  },
  mounted() {
    this.$store.dispatch("categoryList");
    if(this.$route.path!='/home')
    {
      this.show=false
    }
  },

默认情况是显示的,挂载mounted就是说跳转一次页面(带有typeNav的页面),typeNav就会挂载完毕一次,这个时候就判断当前页面是不是home,不是的话就false

但是在search也不都是隐藏,鼠标移上去的时候还得显示出来,加上鼠标移动事件,当年我们给父亲加的事件委托了,现在也给父亲加

 <div @mouseleave="leaveIndex" @mouseenter="enterShow">
leaveIndex() {
      //鼠标移出的index=-1
      this.currentIndex = -1;
      this.show=false
    },
enterShow(){
      this.show=true
    }

这样确实给search实现了,结果home又坏了,它不是固定的显示的,鼠标移动它就显示或隐藏

再加一个路径判断:

leaveIndex() {
      //鼠标移出的index=-1
      this.currentIndex = -1;
      if(this.$route.path!='/home')
      {
      this.show=false
      }
    },

(2)添加过渡动画

让菜单栏不是一下出来的而是缓慢出来

过渡动画:前提组件|元素务必要有v-if|v-show指令才可以进行过渡动画

<div class="sort" v-show="show">

class sort有v-show,所以给它加动画,加动画外面得套<transition>

       <transition name="sort">
        <div class="sort" v-show="show">
         、、、
        </div>
        </transition>

动画特效在css样式中:

 //过渡样式(进入)
    .sort-enter{
      height: 0;
    }
    //进入结束
    .sort-enter-to{
      height: 461px;
    }
    //定义动画时间和速率
    .sort-enter-active{
      transition: all .5s linear;
    }

7.解决三级导航ajax请求重复发送的bug

这里的一个小问题,当初我们靠dispatch来请求数据,但是从home到search它就会重新请求一次

mounted() {
    this.$store.dispatch("categoryList");

那么哪个组件是只运行一次呢,app.vue根组件,于是我们把dispatch放到app里面去,一打开页面就会请求然后放到仓库里

main.js虽然也是只执行一次,但是this放它那儿不行啊,组件的身上才有$store

8.合并params与query参数

就是分类那里是query参数,搜索框输入的是params参数,这两个如果都有就都得传过去

在TypeNav里面当年只传了query参数,现在应该还得加上params参数

//判断如果路由跳转带有params,也得传
        if (this.$route.params) {
          location.params = this.$route.params;
          //整理完参数
          location.query = query;
          //路由跳转
          this.$router.push(location);
        }

我在搜索了abc之后点击分类:手机,成功把query参数和params参数都传过去了

当年的Header组件在跳转的时候只带了params参数,现在我们应该也得加上query参数

//如果有query也带走
      if(this.$route.query)
      {
        let location ={name:"search",params:{keyword:this.keyword||undefined}}
        location.query=this.$route.query
        this.$router.push(location)
      }

六、开发Home首页中的ListContainer、Floor组件

1.mock搭建模拟数据

我们的Home组件只有左侧的全部商品分类是请求来的数据,其他都是假数据(ListContainer、Floor都没有),下面我们用mock来开发一下这两个组件,就是模拟数据,需要用到mockjs插件

安装插件:npm i mockjs

2.mock虚拟数据的ajax请求

使用mock步骤:

(1)在src下新建mock文件夹用来放假数据

(2)准备JSON数据,在mock文件夹新建banner.json文件和floor.json文件(注意文件要格式化一下,不能留空格,否则项目跑不起来

(3)把mock数据需要的图片放置到public文件夹中【public文件夹在打包的时候,会把相应的资源原封不动打包到dist文件夹中】,在public文件下新建一个images文件夹,把假数据中用到的图片都复制到里面去

(4)创建mockSerer.js通过mockjs插件实现模拟数据

//引入mockjs
import Mock from 'mockjs'
//引入json数据格式
import banner from './banner.json'
import floor from './floor.json'

//mock数据,第一个参数请求地址,第二个参数请求数据
Mock.mock("/mock/banner",{code:200,data:banner})//模拟首页大的轮播图的数据
Mock.mock("/mock/floor",{code:200,data:floor})

注意:我们的json数据文件都没有对外暴露,因为图片、JSON文件都是默认暴露的,可以直接引入使用

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

//引入mock
import '@/mock/mockServe'

3.制作Banner轮播图的数据

有虚拟数据了,就向服务器发请求要数据呗,存储在vuex的仓库里面(注意这里不是真实的发请求,请求会被浏览器拦截,用的时候就当真实的请求就行)

以前我们请求数据是在api/request下面,现在我们要向mock请求数据不能直接把人家的base:/api(request里我没有写baseurl,路径直接用的http)改了,再新建一个mockAjax.js

baseURL: '/mock',

现在我们把数据放到仓库中了就该用了,把api里的index.js请求函数封装好

//当前这个模块,所有API接口进行统一的管理
//发请求用到axios,引入进来
import requests from "./request";
import mockRequests from './mockAjax'
//三级联动的接口
 export const reqcategoryList=()=>{
    //axios发请求返回promise对象
    return requests({url:'http://gmall-h5-api.atguigu.cn/api/product/getBaseCategoryList',method:'get'})
 }
 //获取banner轮播图结构
export const reqGetBannerList =()=>{
   return mockRequests({url:'/banner',method:'get'})
}

然后开始发请求,ListContainer组件要用到就在它里面发请求,而且是挂载完毕之后发请求

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

我们调用getBannerList函数但是仓库当中是没有这个函数的

const state = {
    //state中数据默认初始值别瞎写,服务器返回的是对象,服务器返回数组【根据接口返回初始化】
    bannerList:[]
}
const mutations = {
    、、、
    GETBANNERLIST(state,bannerList){
        state.bannerList=bannerList
    },
}
const actions = {
    、、、
    //获取首页轮播图的数据
    async getBannerList({commit}){
        let result=await reqGetBannerList()
        if (result.code == 200) {
            commit("GETBANNERLIST", result.data)
        }
    }
};

相当于服务器请求过来一个数组,我们就得设置一个空数组让空数组等于它

这样仓库中就有一个数组存储数据了,但是组件还没有这个数据呢

import {mapState} from 'vuex'
export default {
  name: "ListContainer",
  mounted() {
    //派发action ,通过Vuex发起ajax请求将数据存储在仓库
    this.$store.dispatch('getBannerList')
  },
  computed:{
    ...mapState({
      bannerList:state=>state.home.bannerList
    })
  }
};

现在组件的身上就有数据了

总结来说就是向mockjs身上要数据,要完了弄到仓库身上,组件从仓库身上获取数据

4.制作ListContainer轮播图

(1)引入相应的包(js和css)

首先安装swiper5版本:npm i swiper@5

然后引入swiper,我们发现banner和下面的家用电器的轮播图样式一样,所以这里不去ListContainer组件引入swiper.css,直接在入口文件引入

//引入swiper样式
import "swiper/css/swiper.css" 

(2)搭建轮播图页面结构

ListContainer:

import Swiper from 'swiper'
<div class="swiper-container" id="mySwiper">
          <div class="swiper-wrapper">
            <div class="swiper-slide" v-for="(carousel,index) in bannerList" :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>

数组直接用,现在的轮播图还是静态的因为还没有new swiper实例

(3)给轮播图添加动态效果

注意:在mounted里面new swiper实例是不对的,因为v-for遍历的是服务器的数据,轮播图数据是动态的,结构还没完整就不能new swiper实例(不是你把四个图都点了才能new,而是请求数据需要一定的时间)

第一种方法:setTimeout定时器(不好)

两秒之后再new swiper实例,实现了功能但是一打开页面两秒之后才能出现轮播图的样式

第二种方法:放在update里(不好)

这样一旦页面发生点变化都得new swiper实例

第三种方法:watch(完美!)

watch监听数据变化,变化的就是bannerList数组,最开始我们设置它为空,后来就有图片了不就是变化了吗

watch: {
    //监听数组变化,由空到有四个元素
    bannerList: {
      handler(newValue, oldValue) {
        //handler要是执行了说明数组变化也就是数据请求过来了
        //但是只是能保证把数据请求过来了,v-for还是需要时间的,所以现在结构还是不完整
        var mySwiper = new Swiper(".swiper", {
          direction: "vertical", // 垂直切换选项
          loop: true, // 循环模式选项
          // 如果需要分页器
          pagination: {
            el: ".swiper-pagination",
            clickable :true,//可以点击小圆点
          },
          // 如果需要前进后退按钮
          navigation: {
            nextEl: ".swiper-button-next",
            prevEl: ".swiper-button-prev",
          },
          // 如果需要滚动条
          scrollbar: {
            el: ".swiper-scrollbar",
          },
        });
      },
    },
  },

carousel就是轮播图的意思

这里的v-for还是没有渲染完成,watch也检测不了它,所以再加上一个技术nextTick

nextTick用法:

在下次DOM更新循环结束之后(就是说数据已经请求回来了、页面的结构已经都有了)执行延迟回调。在修改数据之后(服务器的数据回来了)立即使用这个方法,获取更新后的DOM。

 watch: {
    //监听数组变化,由空到有四个元素
    bannerList: {
      handler(newValue, oldValue) {
        //handler要是执行了说明数组变化也就是数据请求过来了
        //但是只是能保证把数据请求过来了,v-for还是需要时间的,所以现在结构还是不完整
        this.$nextTick(() => {
          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",
            },
          });
        });
      },
    },
  },

补充一个小细节:获取元素不再用dom了,用ref

 <div class="swiper-container" ref="mySwiper">
var mySwiper = new Swiper(this.$refs.mySwiper, 

5.开发floor组件

那几个步骤:先写静态组件、然后写api、写三连环、捞数据、展示数据

(1)从mock拿数据

api:

//获取floor数据
export const reqFloorList =()=>{
   return mockRequests({url:'/floor',method:'get'})
}

三连环就是仿照以前的写,注意初始的时候floor和banner不都是空数组吗,数组不是随便决定的,而是我们的虚拟数据里面他们的数据都是以数组方式写的所以我们也定义为数组(取决于服务器返回的数据)

三连环之后仓库还没有数据呢!

得写一个函数触发action仓库才能有数据,上次的那个dispatch我们不是写ListContainer组件里面了吗,但是这次我们不能写到floor组件里面去,因为我们在home组件进行floor复用了,写里面就不能进行v-for遍历出两个floor组件(虚拟数据里面是数组里面包含两个数据得v-for读取),所以发请求应该在home里面发

import {mapState} from 'vuex'
mounted() {
  this.$store.dispatch("getFloorList")
},
computed:{
  ...mapState({
    floorList:(state)=>state.home.floorList
  })
}

floor标签写一个就行了,v-for遍历数组

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

(2)Home中的数据传给Floor

现在数组还在home中,floor组件的轮播图需要用数据,用props接收

复习一下组件通信的方式有哪些?面试频率极高

props:用于父子组件通信
自定义事件:@on @emit 可以实现子给父通信
全局事件总线:$bus 全能
pubsub-js:vue当中几乎不用 全能
插槽
vuex

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

回到floor组件中props接收一下:

props:['list']

(3)数据渲染到Floor页面

轮播图使用步骤:引包、new Swiper实例

这次写new Swiper实例的时候在mountde里面就可以,因为第一次书写轮播图的时候,是在当前组件内部发请求、动态渲染解构【前台至少服务器数据需要回来】,因此当年的写法在这里不行

现在数据早就被父组件给发过来了,早就把结构完成了,所以mounted可以用

<template>
  <!--楼层-->
  <div class="floor">
    <div class="py-container">
      <div class="title clearfix">
        <h3 class="fl">{{list.name}}</h3>
        <div class="fr">
          <ul class="nav-tabs clearfix">
            <li :class="index == 0 ?'active':'' " v-for="(nav,index) in list.navList" :key="index">
              <a href="#tab1" data-toggle="tab">{{nav.text}}</a>
            </li>
          </ul>
        </div>
      </div>
      <div class="tab-content">
        <div class="tab-pane">
          <div class="floor-1">
            <div class="blockgary">
              <ul class="jd-list">
                <li v-for="(keyword,index) in list.keywords" :key="index">{{keyword}}</li>
              </ul>
              <img :src="list.imgUrl "/>
            </div>
            <div class="floorBanner">
              <!-- 轮播图 -->
              <div class="swiper-container" ref="cur">
                <div class="swiper-wrapper">
                  <div class="swiper-slide" v-for="(carousel,index) in list.carouselList" :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>
            </div>
            <div class="split">
              <span class="floor-x-line"></span>
              <!-- 下面的结构不一样不能用遍历 -->
              <div class="floor-conver-pit">
                <img :src="list.recommendList[0]" />
              </div>
              <div class="floor-conver-pit">
                <img :src="list.recommendList[1]" />
              </div>
            </div>
            <div class="split center">
              <img :src="list.bigImg" />
            </div>
            <div class="split">
              <span class="floor-x-line"></span>
              <div class="floor-conver-pit">
                <img :src="list.recommendList[2]" />
              </div>
              <div class="floor-conver-pit">
                <img :src="list.recommendList[3]" />
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import Swiper from 'swiper'
export default {
  name: "Floor",
  props:['list'],
  mounted() {
    this.$nextTick(() => {
         、、、
      },
};
</script>

注意只有第一个词有样式的方法,格式不一样不能遍历的就直接用数组

(4)把轮播图封装为全局组件

floor的数据用watch监测list监测不了,因为list从来都不变,父亲给它之后什么样就是什么样,不像那个从空数组变成有东西

watch: {
    //监听数组变化,由空到有四个元素
    list: {
      immediate:true,
      //不管数据变没变都执行一次
      handler(newValue, oldValue) {
        //但是只是能保证把数据请求过来了,v-for还是需要时间的,所以现在结构还是不完整
        this.$nextTick(() => {
          var mySwiper = new Swiper(this.$refs.cur, 
          、、、

加上immediate就行

这样写为了和banner的轮播图的结构保持一致,这样就可以进行封装为全局组件(在banner里面也加immediate)

到commponents文件夹中新建一个Carousel文件夹,在它的index.vue中注册,把floor轮播图的东西全捞过来

<template>
  <!-- 轮播图 -->
  <div class="swiper-container" ref="cur">
    <div class="swiper-wrapper">
      <div
        class="swiper-slide"
        v-for="(carousel, index) in 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 {
  name: "Carousel",
  props: ["list"],
  watch: {
    //监听数组变化,由空到有四个元素
    list: {
      immediate: true,
      //不管数据变没变都执行一次
      handler(newValue, oldValue) {
        //但是只是能保证把数据请求过来了,v-for还是需要时间的,所以现在结构还是不完整
        this.$nextTick(() => {
          var mySwiper = new Swiper(this.$refs.cur, {
            、、、

在入口文件中注册:

import Carsousel from '@/components/Carousel'
Vue.component(Carsousel.name,Carsousel)

然后在原来的floor组件原来轮播图的地方直接用:

<Carousel :list="list.carouselList"/>

list是要给全局的轮播图组件传过去的,它还得props接收一下

banner中把轮播图的都删了,直接写组件标签就行

<!--banner轮播-->
        <Carousel :list="bannerList" />
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值