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

2023-07-29,,

写在前面

今天是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项目】尚品汇(三)Home模块+Floor模块+Swiper轮播图的相关教程结束。

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

下载本文的Word格式文档,以方便收藏与打印。