其他分享
首页 > 其他分享> > 【Vue项目】尚品汇(三)Home模块+Floor模块+Swiper轮播图

【Vue项目】尚品汇(三)Home模块+Floor模块+Swiper轮播图

作者:互联网

写在前面

今天是7.23,这一篇内容主要完成了Home模块和部分Search模块的开发,主要是使用了swiper轮播图插件获取vuex仓库数据展示组件以及其他信息。

1 Search模块

1.1 Search模块的全部商品分类的显示

需求分析 :在home模块中,需要显示商品分类的三级联动全局组件,而在search模块则不需要。

①在Search组件中引入typeNav全局组件,并在一级分类元素上添加v-show,在data中添加show数据

<div class="sort" v-show="show">
data() {
    return {
      curIndex: -1,
      show: true
    };
  },

②在TypeNav组件的mounted函数中根据当前路由修改show的值

mounted() {
    // 通知vuex向服务器发送数据,并存储于home数据仓库中
    this.$store.dispatch("CategoryList");
    // 当前组件不是home组件则不显示
    if(this.$route.name!='home') {
      this.show = false;
    }
  },

1.2 Search模块三级联动模块鼠标进出效果

在三级目录父元素上添加leaveShow和enterShow函数

<div @mouseleave="leaveShow()" @mouseenter="enterShow()">

并在方法中定义,其中leavaShow函数需要对当前路由进行判断,home组件的三级联动模块在鼠标移开的时候是不需要消失的

enterShow() {
      this.show = true;
    },
    leaveShow() {
      this.curIndex = -1;
      if(this.$route.name != 'home') {
        this.show = false;
      } 
    },

1.3 Search模块中三级联动模块的过渡动画

transition只能和v-show或者v-if一起使用

<transition name="sort">
  <div class="sort" v-show="show">
      <div class="all-sort-list2">

          <div class="item"  
              v-for="(c1, index) in CateGoryList" :key="c1.categoryId" 
              :class="{cur:index==curIndex}"
              @click="goSearch"
          >

              <h3 @mouseenter="changeIndex(index)">
                  <a :data-cateGoryName="c1.categoryName" :data-cateGory1Id="c1.categoryId">{{c1.categoryName}}</a>
              </h3>
              <div class="item-list clearfix" :style="{display:index==curIndex?'block':'none'}">
                  <div class="subitem" v-for="(c2, index) in c1.categoryChild" :key="c2.categoryId">
                      <dl class="fore">
                          <dt>
                              <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>

定义动画样式:

    // 过渡动画样式
    // 过渡动画开始状态
    .sort-enter{
      height: 0px;
    }
    // 过渡动画结束状态
    .sort-enter-to{
      height: 461px;
    }
    // 定义动画的时间、速率等
    .sort-enter-active{
      transition: .2s linear;
      // 可以解决文字的显示问题
      overflow: hidden;
    }

1.5 TypeNav列表优化

存在的问题:在home和search路由切换的过程里面,换出的路由会被销毁,换入的路由会重新创建,如此一来每次都需要向服务器发送请求请求列表数据,这显然是不合理的,我们需要的是每次使用只请求一次数据,然后放入输入仓库供各模块使用即可。

分析:因为要求只请求一次数据,所以可以在App根组件的mounted中向数据仓库进行派发acion的操作,TypeNav全局组件的mounted就不需要进行重复的操作了。

App.vue

<template>
  <div id="app">
    <Header></Header>
    <router-view></router-view>
    <Footer v-show="$route.meta.showFooter"></Footer>
  </div>
</template>

<script>
import Header from './components/Header'
import Footer from './components/Footer'

export default {
  name: 'App',
  components: {
    Header,
    Footer
  },
  mounted() {
    this.$router.push('/home');
    this.$store.dispatch('CategoryList')
  },
}
</script>

<style>
</style>

TypeNav模块

  mounted() {
    // 通知vuex向服务器发送数据,并存储于home数据仓库中
    // App根组件请求一次,这里注释掉了
    // this.$store.dispatch("CategoryList");

    // 当前组件不是home组件则不显示
    if (this.$route.name != "home") {
      this.show = false;
    }
  },

1.6 合并参数

分析:现在Header组件TypeNav组件都可以通过goSearch方法进行路由跳转到search路由,但是Header组件会向路由通过params参数传递搜索关键字,而三级联动TypeNav组件则是通过query参数向路由传递categoryname以及三级的categoryid参数,因此需要对两种参数进行合并,如果先点击三级联动再点击搜索、或者先点击搜索再点击三级联动,出现的效果应该是两种参数的集合。

实现:在进行路由跳转之前判断一下当前路由是否含有另一个组件的params或者query参数,如果存在则添加到路由器中一起进行传递。

Header模块(使用的是params参数)

    goSearch() {
      let location = {
        name: "search"
      }
      let params = {
        keyWord: this.keyWord
      }
      location.params = params
      if(this.$route.query) {
        location.query = this.$route.query
      }

      this.$router.push(location);
    }

TypeNav模块(使用的是query参数)

    goSearch(event) {
      let element = event.target;
      let {categoryname, category1id, category2id, category3id} =
          element.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;
        // 合并参数
        if (this.$route.params) {
          location.params = this.$route.params
        }
        // 转发路由传递参数
        this.$router.push(location);
      }

注意!!!

想要获取到转发路由器的query参数,需要在路由配置中做如下配置:

path: '/search:keyWord'

否则将无法获取到keyWord参数

2 Home模块

2.1 ListContainer轮播图组件

2.1.1 Mock.js 生成随机书籍 拦截Ajax请求

①安装mockjs(没有点)

npm install --save mockjs

②在项目src文件目录下建立mock文件夹

③将需要模拟的数据以json的形式放在mock下

④并将其中的静态资源放到public下面,这是因为webpack打包之后所有的静态资源会被放到public下面

⑤在mock下面调用引入Mock的mock方法

mock/service.js

import Mock from 'mockjs'

import banner from './banner.json'
import floor from './floor.json'

Mock.mock('/mock/banner', {code: 200, data: banner})
Mock.mock('/mock/floor', {code: 200, data: floor})floor)

webpack中json、图片是默认暴露的

⑥在main.js入口文件中引入mock

main.js

import Vue from 'vue'
import App from './App.vue'
import router from '@/router'
import TypeNav from '@/components/TypeNav'
import store from '@/store'
import '@/mock/service'

Vue.config.productionTip = false;
Vue.component(TypeNav.name, TypeNav);

new Vue({
  render: h => h(App),
  router,
  store
}).$mount('#app')
2.1.2 获取Mock中Banner轮播图的数据

①在home数据仓库中添加actionsmutations以及state

store/home/index.js

import {reqCategoryList} from '@/api'
import {reqBannerList} from "@/api";

const actions = {
    // 通过api中的函数调用,向服务器发送请求,获取服务器数据
    async CategoryList(context) {
        let result = await reqCategoryList();
        if(result.code === 200) {
            context.commit('CATEGORYLIST', result.data)
        }
    },
    async BannerList(context) {
        let result = await reqBannerList();
        if(result.code === 200) {
            context.commit('BANNERLIST',result.data)
        }
    }
}

const mutations = {
    CATEGORYLIST(state, CateGoryList) {
        state.CateGoryList = CateGoryList;
    },
    BANNERLIST(state, BannerList) {
        state.BannerList = BannerList
    }
}

const getters = {

}

const state = {
    CateGoryList: [],
    BannerList: []
}

// 将模块仓库内容对外暴露
export default {
    state,
    actions,
    mutations,
    getters
}

②在ListContainer组件中利用mapState生成对应计算属性

  computed: {
    // mapState生成vuex仓库数据的计算属性
    ...mapState({
      BannerList: (state) => {
        return state.home.BannerList
      }
    })
  }

③在轮播图中展示数据

          <div class="swiper-wrapper">
            <div class="swiper-slide" v-for="(banner, index) in BannerList" :key="banner.id">
              <img :src="banner.imgUrl"/>
            </div>
          </div>
2.1.3 swiper 轮播图插件

①swiper插件安装

npm i --save swiper@5

更高版本的swiper需要vue3

②引入swiper样式

main.js

import 'swiper/css/swiper.css'

③创建轮播图结构

        <div class="swiper-container" id="mySwiper">
          <div class="swiper-wrapper">
            <div class="swiper-slide" v-for="(banner, index) in BannerList" :key="banner.id">
              <img :src="banner.imgUrl"/>
            </div>
          </div>
          <!-- 如果需要分页器 -->
          <div class="swiper-pagination"></div>

          <!-- 如果需要导航按钮 -->
          <div class="swiper-button-prev"></div>
          <div class="swiper-button-next"></div>
        </div>

④new swiper:轮播图添加动态效果

    var mySwiper = new Swiper ('.swiper-container', {
      loop: true, // 循环模式选项

      // 如果需要分页器
      pagination: {
        el: '.swiper-pagination',
      },

      // 如果需要前进后退按钮
      navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev',
      },

      // 如果需要滚动条
      scrollbar: {
        el: '.swiper-scrollbar',
      },
    })

☆在哪里创建swiper实例呢?

1.在mounted生命周期钩子中:结果轮播图无法使用,这是因为mounted是在页面渲染之后运行,但是for循环中的内容牵扯到计算属性需要动态地渲染,这就使得轮播图插件渲染的时候for循环还没有进行,也就是还没有数据,因而轮播图无法呈现。

2.在updated生命周期钩子中:轮播图正常使用,结合updated是在数据动态更新后完成,会导致同一个组件中的其他数据刷新引起轮播图的刷新

3.使用watch监视轮播图无法使用,分析其原因,在于监视属性只能保证BannerList数据发生了变化,但是不能保证for渲染完成,这就使得swiper在for循环之前进行了渲染。

  watch: {
    BannerList: {
      handler(newVal, oldVal) {
        var mySwiper = new Swiper('.swiper-container', {
          loop: true, // 循环模式选项

          // 如果需要分页器
          pagination: {
            el: '.swiper-pagination',
          },

          // 如果需要前进后退按钮
          navigation: {
            nextEl: '.swiper-button-next',
            prevEl: '.swiper-button-prev',
          },

          // 如果需要滚动条
          scrollbar: {
            el: '.swiper-scrollbar',
          },
        })
      }
    }
  }

4.完美解决方案:watch + nextTick:nextTick包裹的函数会在下次DOM更新、循环结束之后执行回调。在修改数据之后立即使用这个方法,获取更新的DOM。

watch: {
    BannerList: {
      handler(newVal, oldVal) {
        this.$nextTick(() => {
          var mySwiper = new Swiper('.swiper-container', {
            loop: true, // 循环模式选项
            // 自动播放
            autoplay:true,
            // 如果需要分页器
            pagination: {
              el: '.swiper-pagination',
              clickable:true,
            },

            // 如果需要前进后退按钮
            navigation: {
              nextEl: '.swiper-button-next',
              prevEl: '.swiper-button-prev',
            },

            // 如果需要滚动条
            scrollbar: {
              el: '.swiper-scrollbar',
            },
          })
        })
      }
    }
  }

2.2 Floor组件

2.2.1 创建访问请求的api

/api/index.js

export const reqFloorList = () => {
    return mockRequests({
        url: '/floor',
        method: 'get'
    })
}
2.2.2 Vuex三大件

actions

    async FloorList(context) {
        let result = await reqFloorList();
        if (result.code === 200) {
            context.commit('FLOORLIST', result.data)
        }
    }

mutations

    FLOORLIST(state, FloorList) {
        state.FloorList = FloorList
    }

state

const state = {
    CateGoryList: [],
    BannerList: [],
    FloorList: []
}
2.2.3 在App.vue中派发floor的actions
this.$store.dispatch('FloorList')

这样做的好处是应用执行期间只需请求一次,每次路由跳转不必再发送请求,只需向vuex仓库中请求数据即可

2.2.4 在home组件中使用mapstate生成仓库数据的计算属性
    computed: {
      ...mapState({
        FloorList: (state) => {
            return state.home.FloorList
        }
      })
    }
2.2.5 生成floor组件,并进行数据的传送
    <div>
        <!-- 包含三级联动加顶部导航栏 -->
        <TypeNav/>
        <!-- 包含轮播图加右侧尚品汇快报 -->
        <ListContainer/>
        <!-- 今日推荐模块 -->
        <TodayRecommend/>
        <!-- 热卖排行模块 -->
        <Rank/>
        <!-- 猜你喜欢模块 -->
        <Like/>
        <!--  -->
        <Floor v-for="(floor, index) in FloorList" :key="floor.id" :floor="floor"/>
    </div>
2.2.6 Floor props接收数据,并进行一系列数据显示
<script>
import Swiper from "swiper";
export default {
  props: ['floor'],
  mounted() {
    var mySwiper = new Swiper ('.swiper-container', {
      loop: true, // 循环模式选项
      autoplay: true,
      // 如果需要分页器
      pagination: {
        el: '.swiper-pagination',
      },

      // 如果需要前进后退按钮
      navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev',
      },

      // 如果需要滚动条
      scrollbar: {
        el: '.swiper-scrollbar',
      },
    })
  }
}
</script>

注意这里的轮播图创建实例可以直接写在mounted上,这是因为数据是home组件传来的不需要动态生成,即创建实例的时候for循环不需要等待异步请求获取数据,而提前完成了渲染

展示

<template>
  <!--楼层-->
  <div class="floor">
    <div class="py-container">
      <div class="title clearfix">
        <h3 class="fl">{{ floor.name }}</h3>
        <div class="fr">
          <ul class="nav-tabs clearfix">
            <li :class="index == 0 ? 'active':'' " v-for="(nav, index) in floor.navList" :key="index">
              <a :href="nav.url" 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 floor.keywords" :key="index">{{ keyword }}</li>
              </ul>
              <img :src="floor.bigImg"/>
            </div>
            <div class="floorBanner">
              <div class="swiper-container" id="floor1Swiper">
                <div class="swiper-wrapper">
                  <div class="swiper-slide" v-for="(carousel, index) in floor.carouselList" :key="index">
                    <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="floor.recommendList[0]"/>
              </div>
              <div class="floor-conver-pit">
                <img :src="floor.recommendList[1]"/>
              </div>
            </div>
            <div class="split center">
              <img :src="floor.bigImg"/>
            </div>
            <div class="split">
              <span class="floor-x-line"></span>
              <div class="floor-conver-pit">
                <img :src="floor.recommendList[2]"/>
              </div>
              <div class="floor-conver-pit">
                <img :src="floor.recommendList[3]"/>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

2.3 共用组件提取Carsousel

可以观察到,Floor中和Home组件中都使用了轮播图,代码重复率较高,因此可以将代码拿出来成为一个公共的组件:Carsousel

components/Carsousel/index.vue

<template>
  <div class="swiper-container" ref="mySwiper">
    <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: 'Carouse',
  props: ['list'],
  watch: {
    list: {
      immediate: true,
      handler() {
        this.$nextTick(() => {
          var mySwiper = new Swiper('.swiper-container', {
            loop: true, // 循环模式选项
            autoplay: true,
            // 如果需要分页器
            pagination: {
              el: '.swiper-pagination',
              clickable :true,
            },

            // 如果需要前进后退按钮
            navigation: {
              nextEl: '.swiper-button-next',
              prevEl: '.swiper-button-prev',
            },

            // 如果需要滚动条
            scrollbar: {
              el: '.swiper-scrollbar',
            },
          })
        })
      }
    }
  }
}
</script>

<style scoped>

</style>

Home组件中的轮播图数据需要动态地从vuex中获取,因此采用watch+nextTick的方式。而Floor组件是从父组件中获取的数据,因此不需要采用这种方式,但是需要开启immediate模式,因为不设置的话计算属性检测不到开始的时候list的变化。综合两种写法便成了上面的代码。

标签:Vue,轮播,state,模块,import,组件,home,swiper
来源: https://www.cnblogs.com/tod4/p/16511959.html