【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的变化。综合两种写法便成了上面的代码。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步