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

水深无声 2022-04-17 01:56 330阅读 0赞

详情页面动态路由及banner布局

  • 创建一个detail-banner的分支,在router/index.js下添加这个路由,然后在pages文件新建detail文件夹和在detail下新建components文件夹

    1. ![20181111184744999.png][]
  • Detail.vue:

    1. <template>
    2. <div>
    3. <detail-banner></detail-banner>
    4. </div>
    5. </template>
    6. <script>
    7. import DetailBanner from './components/Banner'
    8. export default {
    9. name: 'Datail',
    10. components: {
    11. DetailBanner
    12. }
    13. }
    14. </script>
    15. <style lang="stylus" scoped>
    16. </style>

    banner.vue:

    1. <template>
    2. <div class="banner">
    3. <img
    4. class="banner-img"
    5. src="//img1.qunarzz.com/sight/p0/1612/1d/1d9a740c1f9e0efaa3.img.jpg_600x330_3b0fdac5.jpg"
    6. alt=""
    7. >
    8. <div class="banner-info">
    9. <div class="banner-title">
    10. 成都海昌极地海洋公园(AAAA景区)
    11. </div>
    12. <div class="banner-num">
    13. <i class="iconfont banner-icon"></i>39
    14. </div>
    15. </div>
    16. </div>
    17. </template>
    18. <script>
    19. export default {
    20. name: 'DatailBanner'
    21. }
    22. </script>
    23. <style lang="stylus" scoped>
    24. .banner
    25. overflow hidden
    26. position relative
    27. height 0
    28. padding-bottom 55%
    29. .banner-img
    30. width 100%
    31. .banner-info
    32. display flex
    33. position absolute
    34. left 0
    35. right 0
    36. bottom 0
    37. line-height .6rem
    38. color #ffffff
    39. background-image linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, .8))
    40. .banner-title
    41. flex 1
    42. font-size .28rem
    43. padding 0 .2rem
    44. .banner-num
    45. height .32rem
    46. line-height .32rem
    47. margin-top .14rem
    48. padding 0 .4rem
    49. border-radius .2rem
    50. background rgba(0, 0, 0, .8)
    51. font-size .24rem
    52. .banner-icon
    53. font-size .24rem
    54. margin-right .1rem
    55. </style>

    还有这里使用新的icon,需要在iconfont的项目新加上图片的icon,并下载到本地,把项目的这些替换掉watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dvXzkyMTExMA_size_16_color_FFFFFF_t_70

    还需要把iconfont.css中的base64那行替换成新的iconfont.css中的base64的代码 2018111118535816.png

公用图片画廊组件拆分

新建一个detail-banner分支,并创建一个公用的公用图片画廊组件Gallary.vue

  1. <template>
  2. <div class="container" @click="handleGallaryClick">
  3. <div class="wrapper">
  4. <swiper :options="swiperOptions">
  5. <swiper-slide
  6. v-for="(item,index) of imgs"
  7. :key="index"
  8. >
  9. <img class="gallary-img" :src="item">
  10. </swiper-slide>
  11. <div class="swiper-pagination" slot="pagination"></div>
  12. </swiper>
  13. </div>
  14. </div>
  15. </template>
  16. <script>
  17. export default {
  18. name: 'CommonGallary',
  19. props: {
  20. imgs: {
  21. type: Array,
  22. default () {
  23. return []
  24. }
  25. }
  26. },
  27. data () {
  28. return {
  29. swiperOptions: {
  30. pagination: '.swiper-pagination',
  31. paginationType: 'fraction',
  32. observeParents: true,
  33. observer: true
  34. }
  35. }
  36. },
  37. methods: {
  38. handleGallaryClick () {
  39. this.$emit('close')
  40. }
  41. }
  42. }
  43. </script>
  44. <style lang="stylus" scoped>
  45. .container >>> .swiper-container
  46. overflow inherit
  47. .container
  48. display flex
  49. flex-direction column
  50. justify-content center
  51. z-index 99
  52. position fixed
  53. left 0
  54. right 0
  55. top 0
  56. bottom 0
  57. background #000000
  58. .wrapper
  59. width 100%
  60. height 0
  61. padding-bottom 100%
  62. .gallary-img
  63. width 100%
  64. .swiper-pagination
  65. color #ffffff
  66. bottom -1rem
  67. </style>

上面的代码使用的props来实现子组件来得到父组件传来的值,使用$emit向父组件传递方法(同时也可以把值一起传到父组件)

父组件Banner.vue

  1. <template>
  2. <div>
  3. <div class="banner" @click="handleBannerClick">
  4. <img
  5. class="banner-img"
  6. src="//img1.qunarzz.com/sight/p0/1612/1d/1d9a740c1f9e0efaa3.img.jpg_600x330_3b0fdac5.jpg"
  7. alt=""
  8. >
  9. <div class="banner-info">
  10. <div class="banner-title">
  11. 成都海昌极地海洋公园(AAAA景区)
  12. </div>
  13. <div class="banner-num">
  14. <i class="iconfont banner-icon"></i>39
  15. </div>
  16. </div>
  17. </div>
  18. <common-gallary
  19. :imgs="imgs"
  20. v-show="showGallary"
  21. @close="handleGallaryClose"
  22. ></common-gallary>
  23. </div>
  24. </template>
  25. <script>
  26. import CommonGallary from 'common/gallary/Gallary'
  27. export default {
  28. name: 'DatailBanner',
  29. components: {
  30. CommonGallary
  31. },
  32. data () {
  33. return {
  34. showGallary: false,
  35. imgs:['http://img1.qunarzz.com/sight/p0/1612/ca/cae19b693b872602a3.img.jpg_350x240_8c2c93d2.jpg',
  36. 'http://img1.qunarzz.com/sight/p0/1612/4c/4c8c800abd2e8aafa3.img.jpg_350x240_360ff6aa.jpg']
  37. }
  38. },
  39. methods: {
  40. handleBannerClick () {
  41. this.showGallary = true;
  42. },
  43. handleGallaryClose () {
  44. this.showGallary = false;
  45. }
  46. }
  47. }
  48. </script>
  49. <style lang="stylus" scoped>
  50. .banner
  51. overflow hidden
  52. position relative
  53. height 0
  54. padding-bottom 55%
  55. .banner-img
  56. width 100%
  57. .banner-info
  58. display flex
  59. position absolute
  60. left 0
  61. right 0
  62. bottom 0
  63. line-height .6rem
  64. color #ffffff
  65. background-image linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, .8))
  66. .banner-title
  67. flex 1
  68. font-size .28rem
  69. padding 0 .2rem
  70. .banner-num
  71. height .32rem
  72. line-height .32rem
  73. margin-top .14rem
  74. padding 0 .4rem
  75. border-radius .2rem
  76. background rgba(0, 0, 0, .8)
  77. font-size .24rem
  78. .banner-icon
  79. font-size .24rem
  80. margin-right .1rem
  81. </style>

上面的代码有两个主要的方法handleBannerClick()和handleGallaryClose(),分别实现的是使公用画廊组件显示和隐藏,handleGallaryClose()是通过子组件向父组件发送信息和方法

实现header渐隐渐显效果

Header.vue代码:

  1. <template>
  2. <div>
  3. <router-link
  4. tag='div'
  5. to="/"
  6. class="header-abs"
  7. v-show="showAbs"
  8. >
  9. <i class="iconfont header-abs-back"></i>
  10. </router-link>
  11. <div
  12. class="header-fixed"
  13. v-show="!showAbs"
  14. :style="opacityStyle"
  15. >
  16. <router-link to='/'>
  17. <div class="iconfont header-fixed-back"></div>
  18. </router-link>
  19. 景点详情
  20. </div>
  21. </div>
  22. </template>
  23. <script>
  24. export default {
  25. name: 'DatailHeader',
  26. data () {
  27. return {
  28. showAbs: true,
  29. opacityStyle: {
  30. opacity: 0
  31. }
  32. }
  33. },
  34. activated () {
  35. window.addEventListener('scroll',this.handleScroll)
  36. },
  37. methods: {
  38. handleScroll () {
  39. const top = document.documentElement.scrollTop
  40. if(top > 60 ) {
  41. let opacity = top /140
  42. opacity = opacity > 1 ? 1 : opacity
  43. this.opacityStyle = { opacity}
  44. this.showAbs = false
  45. } else {
  46. this.showAbs = true
  47. }
  48. }
  49. }
  50. }
  51. </script>
  52. <style lang="stylus" scoped>
  53. @import '~styles/varibles.styl'
  54. .header-abs
  55. position absolute
  56. left .2rem
  57. top .2rem
  58. width .8rem
  59. height .8rem
  60. line-height .8rem
  61. border-radius .4rem
  62. text-align center
  63. background rgba(0, 0, 0, .6)
  64. .header-abs-back
  65. color #ffffff
  66. font-size .4rem
  67. .header-fixed
  68. position fixed
  69. top 0
  70. left 0
  71. right 0
  72. height $headerHeight
  73. line-height $headerHeight
  74. text-align center
  75. color #fff
  76. background $bgColor
  77. font-size .32rem
  78. .header-fixed-back
  79. position absolute
  80. top 0
  81. left 0
  82. width .64rem
  83. text-align center
  84. font-size .4rem
  85. color #ffffff
  86. </style>

实现的效果:在顶部是这样的样式

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dvXzkyMTExMA_size_16_color_FFFFFF_t_70 1

下滑超过60高时样式:

20181205222725693.png

下拉时,下面的样式是逐渐显示出来的,使用:style动态样式实现的,通过window.addEventListener(‘scroll’,this.handleScroll)来监听窗口的滑动,使用document.documentElement.scrollTop来判断滑动距顶部的距离

对全局事件的解绑

在上面的Header.vue组件中使用了全局事件

  1. activated () {
  2. window.addEventListener('scroll',this.handleScroll)
  3. }

上面的代码不止在详情页面中会触发,而且在其他页面也会触发,因为这是一个全局事件,所以这里我们需要使用对这个全局事件进行解绑

  1. // 页面被隐藏或者页面被替换成新的组件时触发的
  2. deactivated () {
  3. window.removeEventListener('scroll',this.handleScroll)
  4. }

上面这段代码就实现了对全局事件的解绑

使用递归组件实现详情页列表

首先创建一个detail-list分支,并创建一个detail-list.vue的组件

  1. <template>
  2. <div>
  3. <div
  4. class="item"
  5. v-for="(item,index) of list"
  6. :key="index"
  7. >
  8. <div class="item-title border-bottom">
  9. <span class="item-title-icon"></span>
  10. {
  11. {item.title}}
  12. </div>
  13. <div v-if="item.children" class="item-children">
  14. <detail-list :list="item.children"></detail-list>
  15. </div>
  16. </div>
  17. </div>
  18. </template>
  19. <script>
  20. export default {
  21. name: 'DetailList',
  22. props: {
  23. list: Array
  24. }
  25. }
  26. </script>
  27. <style lang="stylus" scoped>
  28. .item-title-icon
  29. position relative
  30. left .06rem
  31. top .06rem
  32. display: inline-block
  33. width .36rem
  34. height .36rem
  35. background url(http://s.qunarzz.com/piao/image/touch/sight/detail.png) 0 -.45rem no-repeat
  36. margin-right .1rem
  37. background-size .4rem 3rem
  38. .item-title
  39. line-height .8rem
  40. font-size .32rem
  41. padding .2rem
  42. .item-children
  43. padding 0 .2rem
  44. </style>

上面的这段代码实现了递归实现,使用v-if判断是否有children数据,有就把这个children中的数据传到自身上,通过name来调用本身,从而实现了递归

  1. <div v-if="item.children" class="item-children">
  2. <detail-list :list="item.children"></detail-list>
  3. </div>

在Deatil.vue中引入这个组件,并向子组件Lits.vue传入数据list

  1. <template>
  2. <div>
  3. <detail-banner></detail-banner>
  4. <detail-header></detail-header>
  5. <div class="content">
  6. <detail-list :list="list"></detail-list>
  7. </div>
  8. </div>
  9. </template>
  10. <script>
  11. import DetailBanner from './components/Banner'
  12. import DetailHeader from './components/Header'
  13. import DetailList from './components/List'
  14. export default {
  15. name: 'Datail',
  16. components: {
  17. DetailBanner,
  18. DetailHeader,
  19. DetailList
  20. },
  21. data () {
  22. return {
  23. list: [
  24. {
  25. title: '成人票',
  26. children: [{
  27. title: '成人三馆联票',
  28. children: [{
  29. title: '成人三馆联票- 某一连锁店销售'
  30. }]
  31. },{
  32. title: '成人五馆联票'
  33. }]
  34. },
  35. {
  36. title: '学生票'
  37. },
  38. {
  39. title: '儿童票'
  40. },
  41. {
  42. title: '特惠票'
  43. }
  44. ]
  45. }
  46. }
  47. }
  48. </script>
  49. <style lang="stylus" scoped>
  50. .content{
  51. height 50rem
  52. }
  53. </style>

使用Ajax获取动态数据

新建一个分支detail-ajax,并引用一个detail.json来模拟数据
Detail.vue修改:

  1. <template>
  2. <div>
  3. <detail-banner
  4. :sightName="sightName"
  5. :bannerImg = "bannerImg"
  6. :bannerImgs ="gallaryImgs"
  7. ></detail-banner>
  8. <detail-header></detail-header>
  9. <div class="content">
  10. <detail-list :list="list"></detail-list>
  11. </div>
  12. </div>
  13. </template>
  14. <script>
  15. import DetailBanner from './components/Banner'
  16. import DetailHeader from './components/Header'
  17. import DetailList from './components/List'
  18. import axios from 'axios'
  19. export default {
  20. name: 'Datail',
  21. components: {
  22. DetailBanner,
  23. DetailHeader,
  24. DetailList
  25. },
  26. data () {
  27. return {
  28. sightName: '',
  29. bannerImg: '',
  30. gallaryImgs: [],
  31. list: []
  32. }
  33. },
  34. mounted () {
  35. this.getDetailInfo ()
  36. },
  37. methods: {
  38. getDetailInfo () {
  39. axios.get('/api/detail.json',{
  40. params: {
  41. id: this.$route.params.id
  42. }
  43. }).then(this.handeGetDataSucc)
  44. },
  45. handeGetDataSucc (res) {
  46. res = res.data
  47. if (res.ret && res.data) {
  48. const data = res.data
  49. this.sightName = data.sightName
  50. this.bannerImg = data.bannerImg
  51. this.gallaryImgs = data.gallaryImgs
  52. this.list = data.categoryList
  53. }
  54. }
  55. }
  56. }
  57. </script>
  58. <style lang="stylus" scoped>
  59. .content{
  60. height 50rem
  61. }
  62. </style>

Banner.vue修改:

  1. <template>
  2. <div>
  3. <div class="banner" @click="handleBannerClick">
  4. <img class="banner-img" :src="bannerImg">
  5. <div class="banner-info">
  6. <div class="banner-title">
  7. {
  8. {this.sightName}}
  9. </div>
  10. <div class="banner-num">
  11. <i class="iconfont banner-icon"></i>
  12. {
  13. {this.bannerImgs.length}}
  14. </div>
  15. </div>
  16. </div>
  17. <common-gallary
  18. :imgs="bannerImgs"
  19. v-show="showGallary"
  20. @close="handleGallaryClose"
  21. ></common-gallary>
  22. </div>
  23. </template>
  24. <script>
  25. import CommonGallary from 'common/gallary/Gallary'
  26. export default {
  27. name: 'DatailBanner',
  28. components: {
  29. CommonGallary
  30. },
  31. props: {
  32. sightName: String,
  33. bannerImg: String,
  34. bannerImgs: Array
  35. },
  36. data () {
  37. return {
  38. showGallary: false,
  39. }
  40. },
  41. methods: {
  42. handleBannerClick () {
  43. this.showGallary = true;
  44. },
  45. handleGallaryClose () {
  46. this.showGallary = false;
  47. }
  48. }
  49. }
  50. </script>
  51. <style lang="stylus" scoped>
  52. .banner
  53. overflow hidden
  54. position relative
  55. height 0
  56. padding-bottom 55%
  57. .banner-img
  58. width 100%
  59. .banner-info
  60. display flex
  61. position absolute
  62. left 0
  63. right 0
  64. bottom 0
  65. line-height .6rem
  66. color #ffffff
  67. background-image linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, .8))
  68. .banner-title
  69. flex 1
  70. font-size .28rem
  71. padding 0 .2rem
  72. .banner-num
  73. height .32rem
  74. line-height .32rem
  75. margin-top .14rem
  76. padding 0 .4rem
  77. border-radius .2rem
  78. background rgba(0, 0, 0, .8)
  79. font-size .24rem
  80. .banner-icon
  81. font-size .24rem
  82. margin-right .1rem
  83. </style>

还有存在一个问题就是拖动在多个页面会相互影响:

  1. router/index.js中加入这段代码:
  2. scrollBehavior (to, from, savedPosition) {
  3. return { x: 0, y: 0 }
  4. }

上面这段代码的作用是每次切换路由时,对应的页面的x轴,y轴都为0

总结一下,组件中name的作用:

  1. 递归组件的时候可以使用name来调用自身
  2. 当我们在页面上对某个页面取消缓存的时候
  3. 在浏览器的vue插件中展开的组件的名字就是name

    1. ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dvXzkyMTExMA_size_16_color_FFFFFF_t_70 2][]

在项目中加入基础动画

新建一个detail-animation分支,并在common/fade下新建一个Fade.vue文件

  1. <template>
  2. <transition>
  3. <slot>
  4. </slot>
  5. </transition>
  6. </template>
  7. <script>
  8. export default {
  9. name: 'Fade'
  10. }
  11. </script>
  12. <style lang="stylus" scoped>
  13. .v-enter, .v-leave-to
  14. opacity 0
  15. .v-enter-active, .v-leave-active
  16. transition opacity .5s
  17. </style>

然后在Banner.vue中使用(要先引用对应的组件并注册,然后就可以使用了,还有其他动画效果可以看我之前的博客:Vue实战项目开发—Vue中的动画特效):

  1. <template>
  2. <div>
  3. <div class="banner" @click="handleBannerClick">
  4. <img class="banner-img" :src="bannerImg">
  5. <div class="banner-info">
  6. <div class="banner-title">
  7. {
  8. {this.sightName}}
  9. </div>
  10. <div class="banner-num">
  11. <i class="iconfont banner-icon"></i>
  12. {
  13. {this.bannerImgs.length}}
  14. </div>
  15. </div>
  16. </div>
  17. <fade-animation>
  18. <common-gallary
  19. :imgs="bannerImgs"
  20. v-show="showGallary"
  21. @close="handleGallaryClose"
  22. ></common-gallary>
  23. </fade-animation>
  24. </div>
  25. </template>
  26. <script>
  27. import CommonGallary from 'common/gallary/Gallary'
  28. import FadeAnimation from 'common/fade/FadeAnimation'
  29. export default {
  30. name: 'DatailBanner',
  31. components: {
  32. CommonGallary,
  33. FadeAnimation
  34. },
  35. props: {
  36. sightName: String,
  37. bannerImg: String,
  38. bannerImgs: Array
  39. },
  40. data () {
  41. return {
  42. showGallary: false,
  43. }
  44. },
  45. methods: {
  46. handleBannerClick () {
  47. this.showGallary = true;
  48. },
  49. handleGallaryClose () {
  50. this.showGallary = false;
  51. }
  52. }
  53. }
  54. </script>
  55. <style lang="stylus" scoped>
  56. .banner
  57. overflow hidden
  58. position relative
  59. height 0
  60. padding-bottom 55%
  61. .banner-img
  62. width 100%
  63. .banner-info
  64. display flex
  65. position absolute
  66. left 0
  67. right 0
  68. bottom 0
  69. line-height .6rem
  70. color #ffffff
  71. background-image linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, .8))
  72. .banner-title
  73. flex 1
  74. font-size .28rem
  75. padding 0 .2rem
  76. .banner-num
  77. height .32rem
  78. line-height .32rem
  79. margin-top .14rem
  80. padding 0 .4rem
  81. border-radius .2rem
  82. background rgba(0, 0, 0, .8)
  83. font-size .24rem
  84. .banner-icon
  85. font-size .24rem
  86. margin-right .1rem
  87. </style>

发表评论

表情:
评论列表 (有 0 条评论,330人围观)

还没有评论,来说两句吧...

相关阅读