跟我一起做一个vue的小项目(APPvue2.5完结篇)

时间:2023-03-09 22:35:39
跟我一起做一个vue的小项目(APPvue2.5完结篇)

先放一下这个完结项目的整体效果

跟我一起做一个vue的小项目(APPvue2.5完结篇)

下面跟我我一起进行下面项目的进行吧~~~

接下来我们进行的是实现header的渐隐渐显效果,并且点击返回要回到首页

我们先看效果

跟我一起做一个vue的小项目(APPvue2.5完结篇)

在处理详情页向下移动过程中,header页出现,我们使用的是监听滚动的方法,然后动态传入样式

//src\pages\detail\components\Header.vue
<template>
<div>
<router-link
class="header-abs"
tag="div"
to="/"
v-show="showAbs"
>
<div class="iconfont header-abs-back">

</div>
</router-link>
<div
class="header-fixed"
v-show="!showAbs"
:style="opacityStyle"
>
<router-link to="/">
<div class="iconfont header-icon-back"></div>
</router-link>
景点详情
</div>
</div>
</template>
<script>
export default {
name: 'DetailHeader',
data () {
return {
showAbs: true,
opacityStyle: {
opacity: 0
}
}
},
methods: {
handleScroll () {
console.log('滾動艦艇', document.documentElement.scrollTop)
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>
<style lang="stylus" scoped>
@import '~styles/varibles.styl';
.header-abs
position:absolute
left:.2rem
top:.2rem
width:.8rem
height:.8rem
border-radius:.4rem
background:rgba(0,0,0,0.8)
text-align:center
line-height:.8rem
.header-abs-back
color:#fff
font-size:.4rem
.header-fixed
height :$headerHeight
line-height:$headerHeight
overflow:hidden
position:fixed
top:0
left:0
right:0
text-align:center
color:#fff
background:$bgColor
font-size:.32rem
.header-icon-back
width:.64rem
text-align:center
font-size:.4rem
position:absolute
top:0
left:0
color:#fff
</style>
//src\pages\detail\Detail.vue
<template>
<div> <detail-banner></detail-banner>
<detail-header></detail-header>
<div class="container"></div>
</div>
</template>
<script>
import DetailBanner from './components/Banner'
import DetailHeader from './components/Header'
export default {
name: 'Detail',
components: {
DetailBanner,
DetailHeader
}
}
</script>
<style lang="stylus" scoped>
.container
height:50rem
</style>

接下来我们对项目中,全局事件进行解绑

当我们在header.vue组件中使用 window.addEventListener('scroll', this.handleScroll)的时候,

我们会发现,在首页中滚动页面也会调用这个事件

跟我一起做一个vue的小项目(APPvue2.5完结篇)

我们使用keep-alive在缓存过程中产生的钩子函数进行解决这个问题

当在这些组件之间切换的时候都会请求一些请求过的数据,每次请求都会导致重复渲染影响性能。这些数据可以存到缓存。此时使用 activate:是在被包裹组件被激活的状态下使用的生命周期钩子,deactivated:在被包裹组件停止使用时调用

我们的header组件中添加

跟我一起做一个vue的小项目(APPvue2.5完结篇)

这样就解决了在首页中,也会进行滚动事件的Bug

//header.vue
<template>
<div>
<router-link
class="header-abs"
tag="div"
to="/"
v-show="showAbs"
>
<div class="iconfont header-abs-back">

</div>
</router-link>
<div
class="header-fixed"
v-show="!showAbs"
:style="opacityStyle"
>
<router-link to="/">
<div class="iconfont header-icon-back"></div>
</router-link>
景点详情
</div>
</div>
</template>
<script>
export default {
name: 'DetailHeader',
data () {
return {
showAbs: true,
opacityStyle: {
opacity: 0
}
}
},
methods: {
handleScroll () {
console.log('滾動艦艇', document.documentElement.scrollTop)
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)
},
// 移除全局事件的影响
dactivated () {
window.removeEventListener('scroll', this.handleScroll)
}
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl';
.header-abs
position:absolute
left:.2rem
top:.2rem
width:.8rem
height:.8rem
border-radius:.4rem
background:rgba(0,0,0,0.8)
text-align:center
line-height:.8rem
.header-abs-back
color:#fff
font-size:.4rem
.header-fixed
height :$headerHeight
line-height:$headerHeight
overflow:hidden
position:fixed
top:0
left:0
right:0
text-align:center
color:#fff
background:$bgColor
font-size:.32rem
.header-icon-back
width:.64rem
text-align:center
font-size:.4rem
position:absolute
top:0
left:0
color:#fff
</style>

接下来我们使用递归组件实现详情页的列表

所谓递归组件是什么?就是在组件里面调用自身。

我们先放一下实现的效果图

跟我一起做一个vue的小项目(APPvue2.5完结篇)

先在detail页面中定义好数据格式,并将数据传递给子组件

//list.vue
<template>
<div> <detail-banner></detail-banner>
<detail-header></detail-header>
<div class="container">
<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: 'Detail',
components: {
DetailBanner,
DetailHeader,
DetailList
},
data () {
return {
list: [
{
title: '成人票',
children: [
{
title: '成人一馆联票',
children: [
{
title: '成人一馆联票-宝安连锁店销售'
},
{
title: '成人一馆联票-龙岗连锁店销售'
},
{
title: '成人一馆联票-坂田连锁店销售'
}
]
},
{
title: '成人二馆联票'
},
{
title: '成人三馆联票'
}
]
},
{
title: '学生票',
children: [
{
title: '学生一馆联票'
},
{
title: '学生二馆联票'
},
{
title: '学生三馆联票'
}
]
},
{
title: '儿童票',
children: [
{
title: '儿童一馆联票',
children: [
{
title: '儿童一馆联票-宝安连锁店销售'
},
{
title: '儿童一馆联票-龙岗连锁店销售'
},
{
title: '儿童一馆联票-坂田连锁店销售'
}
]
},
{
title: '儿童二馆联票'
},
{
title: '儿童三馆联票'
}
]
}
]
}
}
}
</script>
<style lang="stylus" scoped>
// .container
// height:50rem
</style>
//list.vue
//src\pages\detail\components\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
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;
vertical-align: middle;
.item-title
line-height:0.8rem
font-size:.32rem
padding:0 .2rem
.item-children
padding-left:0.6rem
</style>

接下来我们使用ajax来动态渲染数据

我们在mock数据引入detail.json

{
"ret": true,
"data": {
"sightName": "大连圣亚海洋世界(AAAA景区)",
"bannerImg": "http://img1.qunarzz.com/sight/p0/201404/23/04b92c99462687fa1ba45c1b5ba4ad77.jpg_600x330_bf9c4904.jpg",
"commentsNum": 27,
"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": "特惠票"
}
]
}
}

在detail中引入数据

//src\pages\detail\Detail.vue
<template>
<div> <detail-banner
:sightName="sightName"
:bannerImg="bannerImg"
:bannerImgs="gallaryImgs"
></detail-banner>
<detail-header ></detail-header>
<div class="container">
<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: 'Detail',
components: {
DetailBanner,
DetailHeader,
DetailList
},
data () {
return {
sightName: '',
bannerImg: '',
gallaryImgs: [],
list: [
]
}
},
methods: {
getDetailInfo () {
axios.get('/api/detail.json?id=' + this.$route.params, {
params: {
id: this.$route.params.id
}
}).then(this.handleGetDataSucc)
},
handleGetDataSucc (res) {
console.log('res', res)
res = res.data
if (res.ret && res.data) {
const data = res.data
console.log('data', data)
this.sightName = data.sightName
this.bannerImg = data.bannerImg
this.gallaryImgs = data.gallaryImgs
this.list = data.categoryList
}
}
},
mounted () {
this.getDetailInfo()
}
}
</script>
<style lang="stylus" scoped>
// .container
// height:50rem
</style>

将detail中传入的数据,传递给子组件

//src\pages\detail\components\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
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;
vertical-align: middle;
.item-title
line-height:0.8rem
font-size:.32rem
padding:0 .2rem
.item-children
padding-left:0.6rem
</style>
//src\pages\detail\components\Banner.vue
<template>
<div>
<div class="banner" @click="handleBannerClick">
<img :src="bannerImg" alt="" class="banner-img">
<div class="banner-info">
<div class="banner-title">
{{this.sightName}}
</div>
<div class="banner-number">
<span class="iconfont"></span>
{{this.bannerImgs.length}}
</div>
</div>
</div>
<common-gallary
:imgs="bannerImgs"
v-show="showGallery"
@close="handlegalleryClose"
></common-gallary>
</div>
</template>
<script>
import CommonGallary from 'common/gallary/Gallary'
export default {
name: 'DetailBanner',
props: {
sightName: String,
bannerImg: String,
bannerImgs: Array
},
data () {
return {
showGallery: false,
imgs: [
// 'http://img1.qunarzz.com/sight/p55/201211/04/fbcab3e5d6479ce893835fbb.jpg_r_800x800_6360f514.jpg',
// 'http://img1.qunarzz.com/wugc/p180/201306/16/7f08e81624346b1693835fbb.jpg_r_800x800_5f03ad73.jpg'
]
}
},
components: {
CommonGallary
},
methods: {
handleBannerClick () {
this.showGallery = true
},
handlegalleryClose () {
this.showGallery = false
}
}
}
</script>
<style lang="stylus" scoped>
.banner
overflow:hidden
height:0
padding-bottom:55%
position:relative
.banner-img{
width:100%
}
.banner-info
display:flex
position:absolute
left:0
right:0
bottom:0
line-height:0.6rem
background-image:linear-gradient(top,rgba(0,0,0,0),rgba(0,0,0,0.8))
color:#fff
.banner-title
flex:1
font-size:.32rem
padding:0 .2rem
.banner-number
padding:0 .4rem
height:.32rem
line-height:.4rem
margin-top:.24rem
border-radius:.2rem
background:rgba(0,0,0,.8)
font-size:0.24rem
.iconfont
font-size:.24rem </style>

接下来我们给项目添加一些动画效果,点击我们的banner图片,进入gallery图片,给他一个动画的效果

封装动画组件,transition里面的slot插槽,可以插入各种我们要进行动画处理的组件

//src\common\fade\FadeAnimation.vue
<template>
<div>
<transition>
<slot></slot>
</transition>
</div>
</template>
<script>
export default {
name: 'FadeAnimation'
}
</script>
<style lang="stylus" scoped>
.v-enter,.v-leave-to
opacity:0
.v-enter-active,.v-leave-active
transition: opacity 0.5s
</style>

我们在gallery中使用

跟我一起做一个vue的小项目(APPvue2.5完结篇)

效果如下

跟我一起做一个vue的小项目(APPvue2.5完结篇)

我已经将我跟着视频做的项目传到了github上面:https://github.com/JserJser/dailyPush/tree/master/travel

恬不知耻向你们求一个star~~~~~

后记:今天接到了一个电话,大抵是以前实习的时候的,问我同学,说是打我同学电话没有打到,然后问我有没有同他联系。有些唏嘘。

想想毕业已经三年了。

不知道自己对自己的未来有什么期许呢~你想成为什么样的人呢?是不是三年前自己想成为的人呢?

好好看视频,好好学习,好好掌握项目吧~我呀, 还差的很远呢~

第十部分主要讲解的是项目接口联调部分,将mock中的数据放入XAMPP中,还有如何手机真机测试,以及如何打包数据,这部分我就是看了而已,没有实际操作。真实数据是与后端联调。视频的老师给了有些学习vue的建议,可以多多研究vue的一些常见组件的源码,还有研究vue的源码,以及,学习vue的ssr服务端的渲染问题等。

还有很多东西不会,要精通,要很厉害啊~~~