美文网首页Vue.js
Vue.js第8课-项目实战-旅游网站详情页面开发(part03

Vue.js第8课-项目实战-旅游网站详情页面开发(part03

作者: e20a12f8855d | 来源:发表于2019-06-04 09:20 被阅读23次

    六、使用 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 部分的一个简单的动画效果。

    最后记得提交代码并合并分支。

    至此,我们就完成了这个旅游网站的基本代码编写内容,接下来就该对项目进行联调,测试与发布上线了。


    长得好看的都会关注我的 o(≧v≦)o~~

    相关文章

      网友评论

        本文标题:Vue.js第8课-项目实战-旅游网站详情页面开发(part03

        本文链接:https://www.haomeiwen.com/subject/yunkxctx.html