Vue实战项目开发--详情页面开发

详情页面动态路由及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">&#xe64a;</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">&#xe64a;</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">&#xe624;</i>
    </router-link>
    <div 
      class="header-fixed"
      v-show="!showAbs"
      :style="opacityStyle"
    >
      <router-link to='/'>
        <div class="iconfont header-fixed-back">&#xe624;</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">&#xe64a;</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的作用:

  1. 递归组件的时候可以使用name来调用自身
  2. 当我们在页面上对某个页面取消缓存的时候
  3. 在浏览器的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">&#xe64a;</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>

 

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值