六、使用 Ajax 获取动态数据
首先在 static/mock 目录下新建一个 details.json 文件,在里面存放详情页的信息,例如:
details.json
{
"ret": true,
"data": {
"sightName": "北京世界园艺博览会",
"bannerImg": "http://img1.qunarzz.com/sight/p0/1902/84/84696f368bbec10da3.img.jpg_600x330_5ca3e181.jpg",
"gallaryImgs": ["http://img1.qunarzz.com/sight/p0/1902/84/84696f368bbec10da3.img.jpg_350x240_3a0fefe8.jpg", "http://img1.qunarzz.com/sight/p0/1902/3b/3bc5185f2bc00e49a3.img.jpg_350x240_3042e6e0.jpg", "http://img1.qunarzz.com/sight/p0/1902/99/99c0bd5da2c8b9cda3.img.jpg_350x240_d37145b3.jpg"],
"categoryList": [{
"title": "非指定日上午场票",
"children": [{
"title": "[上午场]成人票(早定优惠)"
}, {
"title": "[上午场]老人票"
}]
}, {
"title": "指定日上午场票",
"children": [{
"title": "[上午场]成人票(早定优惠)"
}, {
"title": "[上午场]老人票"
}]
}, {
"title": "儿童票"
}, {
"title": "特惠票"
}]
}
}
然后到 Detail.vue 中通过 axios 获取 detail.json 中的数据,在请求地址这一块需要注意一下,当访问 id 是 001 的景点的时候,应该获取的是 001 这个景点对应的数据,访问 002 获取的就是 002 这个景点对应的数据,所以每一次请求都把这个 id 带给后端,这个 id 是动态路由的一个参数,回忆一下“Vue.js第8课-项目实战-旅游网站详情页面开发(part01)”中的动态路由,那里我们设置了点击不同的城市,地址会根据 id 的不同,而变得不一样。这里,在我们通过 Ajax 向后端请求数据的时候,也需要实现因 id 不同,请求地址也不同的逻辑,所以我们就要在 axios 请求的地址后加一个动态路由的参数,如何获得动态路由的参数呢?
首先看一下路由的配置,打开 router 目录下的 index.js,我们给 detail 这个路径后面加了一个 :id,定义了动态路由,会把对应的 id 存在对应的 id 变量里,那么在请求地址这一块就可以这样去写:
axios.get("/api/detail.json?params" + this.$route.params.id)
他的意思就是给这个请求地址加一个参数,这个参数就是去路由中找到的 id 这个参数,这个时候我们打开页面,例如点击 id 是 001 的城市,到 Network 中看一下,他的请求地址后边跟的就是 detail.json?id=001:
说明能够获取到 001 的 id 值,并发送 ajax 请求。上面这种方式我们直接将参数拼接到了路径后边,其实可以换一种方式,前面只写接口名,后面写一个对象,里边存放需要的参数:
axios.get("/api/detail.json",{
params : {
id : this.$route.params.id
}
})
然后去调用 then 方法,去接收请求到的数据:
Detail.vue
<script>
// ...
import axios from "axios"
export default {
// ...
methods:{
getDeatilInfo(){
// axios.get("/api/detail.json?params" + this.$route.params.id);
// 推荐把参数 params 放到对象中去使用:
axios.get("/api/detail.json",{
params : {
id : this.$route.params.id
}
}).then(this.getDEatilInfoSucc);
},
getDEatilInfoSucc(result){
console.log(result);
}
},
mounted(){
this.getDeatilInfo();
}
};
</script>
打开页面,可以看到,已经成功请求到了数据:
接下来,我们将请求到的数据做下处理,并将这些数据渲染到页面上。
Detail.vue
<template>
<div class="detail-content">
<detail-banner :sightName="sightName" :bannerImg="bannerImg" :gallaryImgs="gallaryImgs"></detail-banner>
<detail-header :sightName="sightName"></detail-header>
<div class="content">
<detail-list :categoryList="categoryList"></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: [],
categoryList: []
};
},
methods: {
getDeatilInfo() {
// axios.get("/api/detail.json?params" + this.$route.params.id)
// 推荐把参数 params 放到对象中去使用:
axios
.get("/api/detail.json", {
params: {
id: this.$route.params.id
}
})
.then(this.getDEatilInfoSucc);
},
getDEatilInfoSucc(result) {
if (result.data) {
var data = result.data.data;
this.sightName = data.sightName;
this.bannerImg = data.bannerImg;
this.gallaryImgs = data.gallaryImgs;
this.categoryList = data.categoryList;
}
}
},
mounted() {
this.getDeatilInfo();
}
};
</script>
<style lang="stylus" scoped>
.detail-content {
height: 20rem;
}
</style>
Detail.vue 中我们通过 axios 请求到了数据,并将数据都给到了各个属性上去,然后再通过属性的方式将这些数据传递给子组件们,最后到各个子组件中通过 props 接收数据并渲染到页面上,banner.vue、
header.vue、list.vue 这几个组件如何去接收数据并渲染我就不多说了。
这个时候详情页的效果应该是这样的:
但是有一个问题,打开 Network,如果点击的是第一个城市,他会去请求:
http://localhost:8080/api/detail.json?id=001
这个路径下的数据,但是返回到首页,我们再点第二个城市,并没有发送新的请求,还是 id=001 的请求,刷新一下页面,才会去请求 id=2 的城市的信息,显然这是不符合逻辑的。导致出现这个问题的原因是什么呢?
回顾一下 keep-alive,我们在 App.vue 中给 router-view 外层包裹了一个 keep-alive 标签,他是 Vue 自带的一个标签,意思就是我的路由的内容被加载一次后,我就把路由中的内容放到内存之中,下一次再进入这个路由的时候,不需要重新渲染这个组件,去重新执行钩子函数,只要去内存里把以前的内容拿出来就可以。我们之前做城市列表页的时候,加了 keep-alive,可以在首页和列表页切换的的时候,不用每次都去请求 index.json 和 list.json,但是在这里,每一个城市的信息内容都是不同的,所以这里讲一个 keep-alive 中的一个属性 exclude,给他添加不想被缓存的页面组件的名字,例如:exclude="detail,意思是除了 detail 页面,其他页面都会被缓存。
这个时候,如果点击的是第一个城市,他会去请求 id=1 的城市信息数据,返回到首页,我们再点第二个城市,就会去请求 id=2 的城市信息。
这样页面就没有问题了么?回到页面上,我们这样去试一下:将首页往上滚动一部分,然后去点击城市,进入详情页,发现详情页也被向上滚动了同样的高度,也就是这个滚动在多个页面之间会互相影响,怎么解决这个 BUG 呢?打开 Vue 官网,找到 vue-router 下的滚动行为,官网给我们提供了一个方法 scrollBehavior,我们将这个方法添加到 router/index.js 中:
router/index.js
import Vue from "vue";
import Router from "vue-router";
import Home from "@/pages/home/Home";
import City from "@/pages/city/City";
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
}
],
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
});
这样就解决了滚动一个页面,影响多个页面的问题。scrollBehavior 这个方法的意思就是,我滚动了某个页面,当打开新的页面的时候,将新页面的位置定位到 x 轴和 y 轴都为 0 的位置。
我还发现了一个问题,就是代码写到这里,我在详情页滚动页面的时候,头部显示不出来了,原本是有一个渐隐渐现的效果的,现在压根就不显示了,经过苦苦的寻找,找到了原因,是因为 activated 这个生命周期函数不执行了,原因是我在上边用了 exclude,取消了对详情页的缓存,所以详情页中使用的组件也不会被缓存。之前说过,当你使用 keeo-alive 的时候,组件中会多出一个生命周期函数 activted,既然这里设置了让 keep-alive 不包括详情页,就不会多出 activted 这个生命周期函数,那么滚动事件也不会被监听。这里换成声明周期函数 created 就可以了,在 created 中添加监听事件,与之对应的在 decreated 中移除监听事件(因为滚动会影响其他组件,所以在打开新页的时候要移除滚动事件,与之前讲的“对全局事件的解绑”中的 deactivated 意思是一样的)。
以上就完成了使用 Ajax 获取动态数据,记得提交代码并合并分支。
补充:
到这里,我们想一个问题,每个组件都有一个 name 值,例:
detail/list.vue
export default {
name: "DetailList",
}
那这个 name 值究竟是有什么用呢?在被其他组件调用的时候,这个 name 值是否决定了其他组件调用该组件时所使用的名称?例:
Detail.vue
<template>
<detail-list></detail-list>
</template>
<script>
import DetailList from "./components/list";
export default {
name: "Detail",
components: {
DetailList
},
}
可以看到,父组件 Detail.vue 引入并调用子组件的时候,确实使用了子组件 list.vue 的 name 值:DetailList,但这并不能说明子组件的 name 值就决定了调用他的组件使用他时就是 name 这个名字,我么可以这样试一下,把父组件 Detail.vue 中引入并调用子组件时的名字换一下:
Detail.vue
<template>
<detail-list000></detail-list000>
</template>
<script>
import DetailList000 from "./components/list";
export default {
name: "Detail",
components: {
DetailList000
},
}
上面我们将 DetailList 都换换成了 DetailList000,打开页面,可以看到页面显示依然正常,那就说明子组件中的 name 值和父组件引用并调用他时的名字并没有关系。那这个 name 值究竟有什么用呢?
目前我们可以知道他在下面这三个地方有用到过:
- 做递归组件的时候;
- 取消页面的缓存;
- 浏览器 Vue Devtools 查看对应的组件。
回忆一下上面我们使用递归组件实现详情页列表的时候,我们在 detail/list.vue 这个组件中,又调用了一次组件自身,因为是同一个组件,所以并不用 import 来引入,而是直接用组件的 name 值就可以了,例:
list.vue
<template>
<div>
<detail-list :list="item.children"></detail-list>
</div>
</template>
<script>
export default {
name: "DetailList",
props: {
list: Array
}
};
</script>
为了验证组件调用自身的时候,就是通过 name 来调用的,我们可以将 DetailList 改为 DetailList000:
list.vue
<template>
<div>
<detail-list000 :list="item.children"></detail-list000>
</div>
</template>
<script>
export default {
name: "DetailList000",
props: {
list: Array
}
};
</script>
回到页面上,可以看到显示依然没有问题。
还记得之前在详情页 detail.vue 中通过 axios 请求数据么? 因为我们在根组件 App.vue 中使用了 keep-alive,但是每个详情页的内容是不同的,请求内容信息的地址也是不同的,所以这里使用了 exclude,意思是除了 exclude 内的页面,其他页面都会被缓存,exclude 中写的就是要取消缓存的那个页面的 name 值。
最后一个地方就是浏览器 Vue Devtools 查看对应的组件的时候,我们可以给浏览器安装一个插件 Vue Devtools,方便我们去查看组件的结构,以及组件的一些参数和配置。
七、在项目中加入基础动画
当点击详情页的时候,会出现一个画廊,点击画廊,他又会消失,如果想给这一过程加一个动画效果,一个渐隐渐显的效果,该如何实现?回忆下一 “Vue.js第4课-Vue中的动画特效(part01)”中实现动画特效的方法。
这里我们先在公共组件目录 common 下新建一个存放动画组件的目录 animation,然后在该目录下新建一个 fade.vue,子里边编写渐隐渐显的动画效果,这里我们使用插槽的方式来实现:
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 0.5s;
}
</style>
接下来去 detail/banner.vue 中引入并使用这个组件,因为我们使用的是 slot 插槽的形式,所以在 banner.vue 中直接将画廊组件 common-gallary 放到 fade 模板中就可以了,这样就相当于给画廊组件外层包了一个 transition,并给他加上了动画效果:
detail/banner.vue
<template>
<div>
<!-- ... -->
<fade>
<common-gallary :imgs="gallaryImgs" v-show="showGallary" @close="GalleryClose"></common-gallary>
</fade>
</div>
</template>
<script>
// ...
import fade from "common/animation/fade";
export default {
name: "DetailBanner",
components : {
fade
}
// ...
};
</script>
此时打开页面,当我们点击详情页的 banner 部分时,画廊会渐渐的显示出来,点击画廊部分,他会渐渐的消失,以上就完成了 banner 部分的一个简单的动画效果。
最后记得提交代码并合并分支。
至此,我们就完成了这个旅游网站的基本代码编写内容,接下来就该对项目进行联调,测试与发布上线了。
网友评论