去哪儿APP vue 2.X —— 开发笔记(六)详情页动态路由和banner布局

138 阅读2分钟

1 banner

image.png

1.1 动态路由

  • 给首页的 recommend 标签包裹 router-link
    • vue 把 router-link 渲染 成 a 标签,a 标签的样式需要处理(变蓝,下划线)。
    • vue 把 router-link 替换成 li 标签,a 标签样式不用处理。 要加上属性 "tag=li" image.png
    • router->index.js加上动态路由 image.png

1.2 细节处理

  • 渐变色
    background-image: linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.8))
    
  • inconfont 每下载一次,代码会变?要及时更新

1.3 画廊 组件拆分 轮播组件

image.png

bulid->webpack.base.config.js,添加一个别名 alias common。

 resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      ......
      'common': resolve('src/common')
    }
  },

banner 组件中,import Gallary子组件

<template>
  <div>
    <div class="banner" @click="handleBannerClick">
      <common-gallary :imgs="imgUrl" v-show="showGallary" @close="handleGallaryClose"></common-gallary>
    </div>
  </div>
</template>
<script>
import CommonGallary from 'common/gallary/Gallary'
export default {
  name: 'DetailBanner',
  data () {
    return {
      imgUrl: ['http://img1.qunarzz.com/sight/p0/1606/d1/d1cd47a17354fc0690.img.jpg_350x240_03b11936.jpg', 'http://img1.qunarzz.com/sight/p0/1606/10/10dfd9bb220c7cc590.img.jpg_350x240_2cccce17.jpg'],
      showGallary: false
    }
  },
  components: {
    CommonGallary
  },
  methods: {
    handleBannerClick () {
      this.showGallary = true
    },
    handleGallaryClose () {
      this.showGallary = false
    }
  }
}
</script>

编写 Gallary 组件代码,src->common->gallary->Gallary.vue,轮播组件参考开发笔记(二)。

<template>
    <div class="container" @click="handleGallaryClick">
        <div class="wrapper">
            <swiper :options="swiperOptions">
                <swiper-slide v-for="(item,index) in imgs" :key="index">//列表遍历
                    <img class="swiper-img" :src="item">
                </swiper-slide>
                <div class="swiper-pagination" slot="pagination"></div>
            </swiper>
        </div>
    </div>
</template>
<script>
export default {
  name: 'CommonGallary',
  props: {
    imgs: Array
  },
  data () {
    return {
      swiperOptions: {
        pagination: '.swiper-pagination',
        paginationType: 'fraction',
        observer: true,
        observeParents: true
      }
    }
  },
  methods: {
    handleGallaryClick () {
      this.$emit('close') //向外触发close事件,点击后关闭当前组件 设置为不可见
    }
  }
}
</script>

1.4 header 渐隐渐现的效果

image.png

1.4.1 JS 更改 DOM 元素的内联样式 opacity

JS 获取页面 scroll 的距离,通过距离和 opacity 的关系,计算opacity。

<template>
  <div>
    <router-link tag="div" to="/" class="header-abs" v-show="showAbs">
      <div class="iconfont header-abs-back">&#xe606;</div>
    </router-link>
    <div class="header-fixed" v-show="!showAbs" :style="OpacityStyle">
      <router-link to="/">
        <div class="iconfont header-fixed-back">&#xe606;</div>
      </router-link>
      景点详情
    </div>
  </div>
</template>
<script>
export default {
  name: 'DetailHeader',
  data () {
    return {
      showAbs: true,
      OpacityStyle: {
        opacity: 0
      }
    }
  },
  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
      }
    }
  },
  activated () {
    window.addEventListener('scroll', this.handleScroll)
  }
}
</script>

1.4.2 对全局事件的解绑 (!import

上述代码

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

带来的问题!

事件没有绑定到 header 组件中,而是绑定到了 window 上,全局都有效果,对其他组件也产生了效果 deactivated页面即将被隐藏,即将被替换成新的页面的时候,执行该钩子函数。

解决:解绑

deactivated () {
    window.removeEventListener('scroll', this.handleScroll)
}

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

image.png

面对这样的数据如何展示?

list: [{
        title: '成人票',
        children: [{
          title: '成人三馆联票',
          children: [{
            title: '成人三馆联票 - 某一连锁店销售'
          }]
        }, {
          title: '成人五馆联票'
        }]
      }, {
        title: '学生票'
      }, {
        title: '儿童票'
      }, {
        title: '特惠票'
      }]

通过递归组件的形式:

<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 class="item-children" v-if="item.children"><!--如果当前item还有孩子就展示该div,并遍历当前item的孩子-->
            <detail-list :list="item.children"></detail-list> <!--🎈🎈递归组件-->
        </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'DetailList',//给组件命名可以在父组件中直接使用,也可以在需要递归组件时直接使用
  props: {
    list: Array
  }
}

2 Ajax 请求数据

2.1 动态路由(接上步骤)

每次 ajax 请求希望把 id 带给后端,访问对应 id 的数据。id是动态路由的参数,可以通过this.$route.params.id获取。

<template>
  <div>
  </div>
</template>
<script>
import axios from 'axios'//🎈
export default {
  name: 'Detail',
  components: {
  },
  methods: {
    getDetailInfo () {
      axios.get('/api/detail.json?id=' + this.$route.params.id)//🎈相当于下面的代码
    }
  },
  mounted () {
    this.getDetailInfo()//🎈
  }
}
</script>

上述功能代码写好,发现只有在首次进 detail 页面的时候才会发 ajax 请求。
原因是: detail 通过 keep-ali 做了缓存 ,所以 mounted 只会执行一次。
解决:若想每一次重新进页面的时候都重新发一次 ajax 请求, 那就要

  • 法一:参考 Home.vue,配合使用 activated 生命周期钩子
  • 法二:在 keep-alive 的时候 exclude 掉 Detail 页面,Detail 页面便不会被缓存了。每次进页面 mounted 都会被执行,App.vue:
    <keep-alive exclude="Detail">
      <router-view/>
    </keep-alive>
    
    • 注:当 exclude 某个页面后,这个页面及其单页面子组件中 activated 钩子函数也要换回 mounted

2.2 Ajax 请求数据全流程

参见 Home.vue 案例 ajax

2.3 滚动行为 每次路由切换到新的页面 scroll 始终都在顶部

滚动行为: image.png 代码的意思:每次进行路由切换的时候,让显示的页面x初始为0,y初始也为0,始终回到最顶部。加到 router->index.js 中。

  scrollBehavior (to, from, savedPosition) {
    return { x: 0, y: 0 }
  }

2.4 思考: 给组件的命名 name 什么时候能用到?

  • 在父组件中 import 子组件,并在 template 中直接用标签展示子组件。
  • 递归组件。
  • 设置了 keep-alive 后,想取消对某个页面的缓存的时候,用到 exclude = XXX
  • 便于在 Chrome 中使用 vue 调试工具的时候 看的更直观