【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数据仓库中添加actions
和mutations
以及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