Vue实战项目开发--详情页面开发
详情页面动态路由及banner布局创建一个detail-banner的分支,在router/index.js下添加这个路由,然后在pages文件新建detail文件夹和在detail下新建components文件夹 Detail.vue:<template><div><detail-banner>
详情页面动态路由及banner布局
- 创建一个detail-banner的分支,在router/index.js下添加这个路由,然后在pages文件新建detail文件夹和在detail下新建components文件夹
- Detail.vue:
<template> <div> <detail-banner></detail-banner> </div> </template> <script> import DetailBanner from './components/Banner' export default { name: 'Datail', components: { DetailBanner } } </script> <style lang="stylus" scoped> </style>
banner.vue:
<template> <div class="banner"> <img class="banner-img" src="//img1.qunarzz.com/sight/p0/1612/1d/1d9a740c1f9e0efaa3.img.jpg_600x330_3b0fdac5.jpg" alt="" > <div class="banner-info"> <div class="banner-title"> 成都海昌极地海洋公园(AAAA景区) </div> <div class="banner-num"> <i class="iconfont banner-icon"></i>39 </div> </div> </div> </template> <script> export default { name: 'DatailBanner' } </script> <style lang="stylus" scoped> .banner overflow hidden position relative height 0 padding-bottom 55% .banner-img width 100% .banner-info display flex position absolute left 0 right 0 bottom 0 line-height .6rem color #ffffff background-image linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, .8)) .banner-title flex 1 font-size .28rem padding 0 .2rem .banner-num height .32rem line-height .32rem margin-top .14rem padding 0 .4rem border-radius .2rem background rgba(0, 0, 0, .8) font-size .24rem .banner-icon font-size .24rem margin-right .1rem </style>
还有这里使用新的icon,需要在iconfont的项目新加上图片的icon,并下载到本地,把项目的这些替换掉
还需要把iconfont.css中的base64那行替换成新的iconfont.css中的base64的代码
公用图片画廊组件拆分
新建一个detail-banner分支,并创建一个公用的公用图片画廊组件Gallary.vue
<template>
<div class="container" @click="handleGallaryClick">
<div class="wrapper">
<swiper :options="swiperOptions">
<swiper-slide
v-for="(item,index) of imgs"
:key="index"
>
<img class="gallary-img" :src="item">
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
</div>
</div>
</template>
<script>
export default {
name: 'CommonGallary',
props: {
imgs: {
type: Array,
default () {
return []
}
}
},
data () {
return {
swiperOptions: {
pagination: '.swiper-pagination',
paginationType: 'fraction',
observeParents: true,
observer: true
}
}
},
methods: {
handleGallaryClick () {
this.$emit('close')
}
}
}
</script>
<style lang="stylus" scoped>
.container >>> .swiper-container
overflow inherit
.container
display flex
flex-direction column
justify-content center
z-index 99
position fixed
left 0
right 0
top 0
bottom 0
background #000000
.wrapper
width 100%
height 0
padding-bottom 100%
.gallary-img
width 100%
.swiper-pagination
color #ffffff
bottom -1rem
</style>
上面的代码使用的props来实现子组件来得到父组件传来的值,使用$emit向父组件传递方法(同时也可以把值一起传到父组件)
父组件Banner.vue
<template>
<div>
<div class="banner" @click="handleBannerClick">
<img
class="banner-img"
src="//img1.qunarzz.com/sight/p0/1612/1d/1d9a740c1f9e0efaa3.img.jpg_600x330_3b0fdac5.jpg"
alt=""
>
<div class="banner-info">
<div class="banner-title">
成都海昌极地海洋公园(AAAA景区)
</div>
<div class="banner-num">
<i class="iconfont banner-icon"></i>39
</div>
</div>
</div>
<common-gallary
:imgs="imgs"
v-show="showGallary"
@close="handleGallaryClose"
></common-gallary>
</div>
</template>
<script>
import CommonGallary from 'common/gallary/Gallary'
export default {
name: 'DatailBanner',
components: {
CommonGallary
},
data () {
return {
showGallary: false,
imgs:['http://img1.qunarzz.com/sight/p0/1612/ca/cae19b693b872602a3.img.jpg_350x240_8c2c93d2.jpg',
'http://img1.qunarzz.com/sight/p0/1612/4c/4c8c800abd2e8aafa3.img.jpg_350x240_360ff6aa.jpg']
}
},
methods: {
handleBannerClick () {
this.showGallary = true;
},
handleGallaryClose () {
this.showGallary = false;
}
}
}
</script>
<style lang="stylus" scoped>
.banner
overflow hidden
position relative
height 0
padding-bottom 55%
.banner-img
width 100%
.banner-info
display flex
position absolute
left 0
right 0
bottom 0
line-height .6rem
color #ffffff
background-image linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, .8))
.banner-title
flex 1
font-size .28rem
padding 0 .2rem
.banner-num
height .32rem
line-height .32rem
margin-top .14rem
padding 0 .4rem
border-radius .2rem
background rgba(0, 0, 0, .8)
font-size .24rem
.banner-icon
font-size .24rem
margin-right .1rem
</style>
上面的代码有两个主要的方法handleBannerClick()和handleGallaryClose(),分别实现的是使公用画廊组件显示和隐藏,handleGallaryClose()是通过子组件向父组件发送信息和方法
实现header渐隐渐显效果
Header.vue代码:
<template>
<div>
<router-link
tag='div'
to="/"
class="header-abs"
v-show="showAbs"
>
<i class="iconfont header-abs-back"></i>
</router-link>
<div
class="header-fixed"
v-show="!showAbs"
:style="opacityStyle"
>
<router-link to='/'>
<div class="iconfont header-fixed-back"></div>
</router-link>
景点详情
</div>
</div>
</template>
<script>
export default {
name: 'DatailHeader',
data () {
return {
showAbs: true,
opacityStyle: {
opacity: 0
}
}
},
activated () {
window.addEventListener('scroll',this.handleScroll)
},
methods: {
handleScroll () {
const top = document.documentElement.scrollTop
if(top > 60 ) {
let opacity = top /140
opacity = opacity > 1 ? 1 : opacity
this.opacityStyle = { opacity}
this.showAbs = false
} else {
this.showAbs = true
}
}
}
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.header-abs
position absolute
left .2rem
top .2rem
width .8rem
height .8rem
line-height .8rem
border-radius .4rem
text-align center
background rgba(0, 0, 0, .6)
.header-abs-back
color #ffffff
font-size .4rem
.header-fixed
position fixed
top 0
left 0
right 0
height $headerHeight
line-height $headerHeight
text-align center
color #fff
background $bgColor
font-size .32rem
.header-fixed-back
position absolute
top 0
left 0
width .64rem
text-align center
font-size .4rem
color #ffffff
</style>
实现的效果:在顶部是这样的样式
下滑超过60高时样式:
下拉时,下面的样式是逐渐显示出来的,使用:style动态样式实现的,通过window.addEventListener('scroll',this.handleScroll)来监听窗口的滑动,使用document.documentElement.scrollTop来判断滑动距顶部的距离
对全局事件的解绑
在上面的Header.vue组件中使用了全局事件
activated () {
window.addEventListener('scroll',this.handleScroll)
}
上面的代码不止在详情页面中会触发,而且在其他页面也会触发,因为这是一个全局事件,所以这里我们需要使用对这个全局事件进行解绑
// 页面被隐藏或者页面被替换成新的组件时触发的
deactivated () {
window.removeEventListener('scroll',this.handleScroll)
}
上面这段代码就实现了对全局事件的解绑
使用递归组件实现详情页列表
首先创建一个detail-list分支,并创建一个detail-list.vue的组件
<template>
<div>
<div
class="item"
v-for="(item,index) of list"
:key="index"
>
<div class="item-title border-bottom">
<span class="item-title-icon"></span>
{{item.title}}
</div>
<div v-if="item.children" class="item-children">
<detail-list :list="item.children"></detail-list>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DetailList',
props: {
list: Array
}
}
</script>
<style lang="stylus" scoped>
.item-title-icon
position relative
left .06rem
top .06rem
display: inline-block
width .36rem
height .36rem
background url(http://s.qunarzz.com/piao/image/touch/sight/detail.png) 0 -.45rem no-repeat
margin-right .1rem
background-size .4rem 3rem
.item-title
line-height .8rem
font-size .32rem
padding .2rem
.item-children
padding 0 .2rem
</style>
上面的这段代码实现了递归实现,使用v-if判断是否有children数据,有就把这个children中的数据传到自身上,通过name来调用本身,从而实现了递归
<div v-if="item.children" class="item-children">
<detail-list :list="item.children"></detail-list>
</div>
在Deatil.vue中引入这个组件,并向子组件Lits.vue传入数据list
<template>
<div>
<detail-banner></detail-banner>
<detail-header></detail-header>
<div class="content">
<detail-list :list="list"></detail-list>
</div>
</div>
</template>
<script>
import DetailBanner from './components/Banner'
import DetailHeader from './components/Header'
import DetailList from './components/List'
export default {
name: 'Datail',
components: {
DetailBanner,
DetailHeader,
DetailList
},
data () {
return {
list: [
{
title: '成人票',
children: [{
title: '成人三馆联票',
children: [{
title: '成人三馆联票- 某一连锁店销售'
}]
},{
title: '成人五馆联票'
}]
},
{
title: '学生票'
},
{
title: '儿童票'
},
{
title: '特惠票'
}
]
}
}
}
</script>
<style lang="stylus" scoped>
.content{
height 50rem
}
</style>
使用Ajax获取动态数据
新建一个分支detail-ajax,并引用一个detail.json来模拟数据
Detail.vue修改:
<template>
<div>
<detail-banner
:sightName="sightName"
:bannerImg = "bannerImg"
:bannerImgs ="gallaryImgs"
></detail-banner>
<detail-header></detail-header>
<div class="content">
<detail-list :list="list"></detail-list>
</div>
</div>
</template>
<script>
import DetailBanner from './components/Banner'
import DetailHeader from './components/Header'
import DetailList from './components/List'
import axios from 'axios'
export default {
name: 'Datail',
components: {
DetailBanner,
DetailHeader,
DetailList
},
data () {
return {
sightName: '',
bannerImg: '',
gallaryImgs: [],
list: []
}
},
mounted () {
this.getDetailInfo ()
},
methods: {
getDetailInfo () {
axios.get('/api/detail.json',{
params: {
id: this.$route.params.id
}
}).then(this.handeGetDataSucc)
},
handeGetDataSucc (res) {
res = res.data
if (res.ret && res.data) {
const data = res.data
this.sightName = data.sightName
this.bannerImg = data.bannerImg
this.gallaryImgs = data.gallaryImgs
this.list = data.categoryList
}
}
}
}
</script>
<style lang="stylus" scoped>
.content{
height 50rem
}
</style>
Banner.vue修改:
<template>
<div>
<div class="banner" @click="handleBannerClick">
<img class="banner-img" :src="bannerImg">
<div class="banner-info">
<div class="banner-title">
{{this.sightName}}
</div>
<div class="banner-num">
<i class="iconfont banner-icon"></i>
{{this.bannerImgs.length}}
</div>
</div>
</div>
<common-gallary
:imgs="bannerImgs"
v-show="showGallary"
@close="handleGallaryClose"
></common-gallary>
</div>
</template>
<script>
import CommonGallary from 'common/gallary/Gallary'
export default {
name: 'DatailBanner',
components: {
CommonGallary
},
props: {
sightName: String,
bannerImg: String,
bannerImgs: Array
},
data () {
return {
showGallary: false,
}
},
methods: {
handleBannerClick () {
this.showGallary = true;
},
handleGallaryClose () {
this.showGallary = false;
}
}
}
</script>
<style lang="stylus" scoped>
.banner
overflow hidden
position relative
height 0
padding-bottom 55%
.banner-img
width 100%
.banner-info
display flex
position absolute
left 0
right 0
bottom 0
line-height .6rem
color #ffffff
background-image linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, .8))
.banner-title
flex 1
font-size .28rem
padding 0 .2rem
.banner-num
height .32rem
line-height .32rem
margin-top .14rem
padding 0 .4rem
border-radius .2rem
background rgba(0, 0, 0, .8)
font-size .24rem
.banner-icon
font-size .24rem
margin-right .1rem
</style>
还有存在一个问题就是拖动在多个页面会相互影响:
在router/index.js中加入这段代码:
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
上面这段代码的作用是每次切换路由时,对应的页面的x轴,y轴都为0
总结一下,组件中name的作用:
- 递归组件的时候可以使用name来调用自身
- 当我们在页面上对某个页面取消缓存的时候
- 在浏览器的vue插件中展开的组件的名字就是name
在项目中加入基础动画
新建一个detail-animation分支,并在common/fade下新建一个Fade.vue文件
<template>
<transition>
<slot>
</slot>
</transition>
</template>
<script>
export default {
name: 'Fade'
}
</script>
<style lang="stylus" scoped>
.v-enter, .v-leave-to
opacity 0
.v-enter-active, .v-leave-active
transition opacity .5s
</style>
然后在Banner.vue中使用(要先引用对应的组件并注册,然后就可以使用了,还有其他动画效果可以看我之前的博客:Vue实战项目开发--Vue中的动画特效):
<template>
<div>
<div class="banner" @click="handleBannerClick">
<img class="banner-img" :src="bannerImg">
<div class="banner-info">
<div class="banner-title">
{{this.sightName}}
</div>
<div class="banner-num">
<i class="iconfont banner-icon"></i>
{{this.bannerImgs.length}}
</div>
</div>
</div>
<fade-animation>
<common-gallary
:imgs="bannerImgs"
v-show="showGallary"
@close="handleGallaryClose"
></common-gallary>
</fade-animation>
</div>
</template>
<script>
import CommonGallary from 'common/gallary/Gallary'
import FadeAnimation from 'common/fade/FadeAnimation'
export default {
name: 'DatailBanner',
components: {
CommonGallary,
FadeAnimation
},
props: {
sightName: String,
bannerImg: String,
bannerImgs: Array
},
data () {
return {
showGallary: false,
}
},
methods: {
handleBannerClick () {
this.showGallary = true;
},
handleGallaryClose () {
this.showGallary = false;
}
}
}
</script>
<style lang="stylus" scoped>
.banner
overflow hidden
position relative
height 0
padding-bottom 55%
.banner-img
width 100%
.banner-info
display flex
position absolute
left 0
right 0
bottom 0
line-height .6rem
color #ffffff
background-image linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, .8))
.banner-title
flex 1
font-size .28rem
padding 0 .2rem
.banner-num
height .32rem
line-height .32rem
margin-top .14rem
padding 0 .4rem
border-radius .2rem
background rgba(0, 0, 0, .8)
font-size .24rem
.banner-icon
font-size .24rem
margin-right .1rem
</style>
更多推荐
所有评论(0)