详情页&对全局事件解绑&递归组件实现详情页

项目地址
视频演示地址


详情页动态路由及banner布局

实现点击以下能够进入详情页

点击进入详情页

src\pages\home\components\Recommend.vue中router-link

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<router-link
tag="li"
class="item border-bottom"
v-for="item of list"
:key="item.id"
:to="/detail/ + item.id"
>
<img class="item-img" :src="item.imgUrl" alt="" />

<div class="item-info">
<p class="item-title">{{ item.title }}</p>
<p class="item-desc">{{ item.desc }}</p>
<button class="item-button">查看详情</button>
</div>
</router-link>

src\router\index.js路由配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import  Detail  from '@/pages/detail/Detail'
Vue.use(Router)

export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},{
path:'/city',
name:'City',
component:City
},{
path:'/detail/:id',
name:'Detail',
component:Detail
}
]
})

src\pages\detail\Detail.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
<detail-banner></detail-banner>
<detail-header></detail-header>
</div>
</template>

<script>
import DetailBanner from "./components/Banner";
import DetailHeader from "./components/Header";
export default {
name: "Detail",
components: {
DetailBanner,
DetailHeader
}
};
</script>

实现效果

详情页对全局事件解绑

全局事件
详情页绑定了一个全局事件,当我在详情页面中滚动,这个样写没有问题,但是当我去到其他页面,在滚动时,你就会发现,刚刚你绑定在详情页中的滚动事件,在这个页面也被执行了,这肯定是有问题的。

其实在我们使用了keep-alive标签后,会有两个生命周期函数分别是:activated、deactivated

activated:页面展示的时候被执行

deactivated:页面被隐藏或者页面即将被替换成新的页面时被执行

1
2
3
4
5
6
activated () {
window.addEventListener('scroll', this.handleScroll)
},
deactivated () {
window.removeEventListener('scroll', this.handleScroll)
}

这段代码是页面被展示的执行scroll,页面被隐藏的时候移除scroll事件

src\pages\detail\components\Header.vue部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<template>
<div>
<router-link tag="div" to="/" class="header-abs" v-show="showAbs">
<div class="iconfont header-abs-back">&#xe624;</div>
</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: "DetailHeader",
data() {
return {
showAbs: true,
opacityStyle: 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() {
//绑定了一个scroll事件,一旦它被执行,this.handleScroll方法会被执行
window.addEventListener("scroll", this.handleScroll);
},
deactivated() {
window.removeEventListener("scroll", this.handleScroll);
}
};
</script>

递归组件实现详情页

递归组件的意思就是在组件自身调用组件自身。

数据 detail.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"ret": true,
"data": {
"sightName": "大连圣亚海洋世界(AAAA景区)",
"bannerImg": "http://img1.qunarzz.com/sight/p0/201404/23/04b92c99462687fa1ba45c1b5ba4ad77.jpg_600x330_bf9c4904.jpg",
"gallaryImgs": ["http://img1.qunarzz.com/sight/p0/201404/23/04b92c99462687fa1ba45c1b5ba4ad77.jpg_800x800_70debc93.jpg", "http://img1.qunarzz.com/sight/p0/1709/76/7691528bc7d7ad3ca3.img.png_800x800_9ef05ee7.png"],
"categoryList": [{
"title": "成人票",
"children": [{
"title": "成人三馆联票",
"children": [{
"title": "成人三馆联票 - 某一连锁店销售"
}]
},{
"title": "成人五馆联票"
}]
}, {
"title": "学生票"
}, {
"title": "儿童票"
}, {
"title": "特惠票"
}]
}
}

src\pages\detail\components\List.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<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>
import Detail from "../Detail.vue";
export default {
components: { Detail },
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: 0.32rem
padding : 0 .2rem
.item-children
padding:0 .2rem
</style>

上面代码中,在 list-children 这个元素下,先做了一个判断,当 item.children 下有值的时候,调用一下自身,也就是 detail-list 这个组件,这个组件也是通过属性的形式,传一个 list,因为在 list.vue 中已经通过 props 接收到 list 了,而且外层已经循环过 list 了,现在要获取 list 下的 children 中的数据,所以直接让这个 list 属性等于 item.children 就可以了。因为数据存在层级关系,可以通过添加样式呈现出来,效果如下图:

效果

keep-alive不缓存

在Detail.vue页面中,当我点击了其他景点后,它也是不会发送请求的,那么Detail页面就不会重新渲染了。

可以使用keep-alive的exclude属性,给它默认设置为Detail,用途是每次进入Detail页面都会发送请求

1
2
3
<keep-alive exclude="Detail">   //使用 exclude 属性,可以设置不需要缓存的页面
<router-view/>
</keep-alive>

页面路由切换回到最顶部

代码添加

让页面切换后始终回到页面最顶部

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

在项目中加入基础动画

让图片点击时有若影若现的效果

新建一个组件,通过插槽的方式传递
src\common\fade\FadeAnimation.vue

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

src\pages\detail\components\Banner.vue中使用,部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    <fade-animation>
<common-gallary
:imgs="bannerImgs"
v-show="showGallary"
@close="handleGallaryClose"
></common-gallary
></fade-animation>

<script>
import CommonGallary from "common/gallary/Gallary";
import FadeAnimation from "common/fade/FadeAnimation";
export default {
name: "DetailBanner",
components: {
CommonGallary,
FadeAnimation
}
};
</script>

至此,项目基本完结…


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!