(10)首页开发——⑤ AJAX 获取首页数据 | Vue.js 项目实战: 移动端“旅游网站”开发

109 阅读9分钟
转载请注明出处,未经同意,不可修改文章内容。

🔥🔥🔥“前端一万小时”两大明星专栏——“从零基础到轻松就业”、“前端面试刷题”,已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。

1 mock 数据

🔗前置知识:
《JavaScript 基础——JS 提供的对象:⑤ JSON》
《Vue 实战准备——② 项目框架源码解析》

在项目开发中,当没有后端的支持时,就需要我们自己模拟数据了。

1️⃣在 static 目录下新建一个 mock 文件夹(用来存放项目中所有的模拟数据),在 mock 里新建一个 index.json 文件(数据是 JSON 格式的): travel10-01.png

1️⃣-①:index.json 中的数据,为首页所有小组件所需的数据;

{
  "ret": true, /* 1️⃣-②:ret 为 true,代表服务器正确响应了请求; */

  "data": {
    "city": "北京", /* 1️⃣-③:data 中有一个 city 为 Header.vue 的“城市”,即当前所在城市; */

    "swiperList": [{ /* 1️⃣-④:轮播组件的数据 swiperList; */
        "id": "0001",
        "imgUrl": "https://qdywxs.github.io/travel-images/swiperList01.jpg"
      },{
        "id": "0002",
        "imgUrl": "https://qdywxs.github.io/travel-images/swiperList02.jpg"
      },{
        "id": "0003",
        "imgUrl": "https://qdywxs.github.io/travel-images/swiperList03.jpg"
      },{
        "id": "0004",
        "imgUrl": "https://qdywxs.github.io/travel-images/swiperList04.jpg"
      }],

    "iconList": [{ /* 1️⃣-⑤:图标区域组件的数据 iconList; */
        "id": "0001",
        "imgUrl": "https://qdywxs.github.io/travel-images/iconList01.png",
        "desc": "景点门票"
      }, {
        "id": "0002",
        "imgUrl": "https://qdywxs.github.io/travel-images/iconList02.png",
        "desc": "滑雪季"
      }, {
        "id": "0003",
        "imgUrl": "https://qdywxs.github.io/travel-images/iconList03.png",
        "desc": "泡温泉"
      }, {
        "id": "0004",
        "imgUrl": "https://qdywxs.github.io/travel-images/iconList04.png",
        "desc": "动植园"
      }, {
        "id": "0005",
        "imgUrl": "https://qdywxs.github.io/travel-images/iconList05.png",
        "desc": "游乐园"
      }, {
        "id": "0006",
        "imgUrl": "https://qdywxs.github.io/travel-images/iconList06.png",
        "desc": "必游榜单"
      }, {
        "id": "0007",
        "imgUrl": "https://qdywxs.github.io/travel-images/iconList07.png",
        "desc": "演出"
      }, {
        "id": "0008",
        "imgUrl": "https://qdywxs.github.io/travel-images/iconList08.png",
        "desc": "城市观光"
      }, {
        "id": "0009",
        "imgUrl": "https://qdywxs.github.io/travel-images/iconList09.png",
        "desc": "一日游"
      }],

    "recommendList": [{ /* 1️⃣-⑥:“热销推荐”组件的数据 recommendList; */
        "id": "0001",
        "imgUrl": "https://qdywxs.github.io/travel-images/recommendList01.jpg",
        "title": "故宫",
        "desc": "东方宫殿建筑代表,世界宫殿建筑典范"
      }, {
        "id": "0002",
        "imgUrl": "https://qdywxs.github.io/travel-images/recommendList02.jpg",
        "title": "南山滑雪场",
        "desc": "北京专业级滑雪圣地"
      }, {
        "id": "0003",
        "imgUrl": "https://qdywxs.github.io/travel-images/recommendList03.jpg",
        "title": "天安门广场",
        "desc": "我爱北京天安门,天安门上太阳升"
      }, {
        "id": "0004",
        "imgUrl": "https://qdywxs.github.io/travel-images/recommendList04.jpg",
        "title": "水立方",
        "desc": "中国的荣耀,阳光下的晶莹水滴"
      }, {
        "id": "0005",
        "imgUrl": "https://qdywxs.github.io/travel-images/recommendList05.jpg",
        "title": "温都水城养生馆",
        "desc": "各种亚热带植物掩映其间,仿佛置身热带雨林"
      }],

    "weekendList": [{ /* 1️⃣-⑦:“周末去哪儿”组件的数据 weekendList。 */
        "id": "0001",
        "imgUrl": "https://qdywxs.github.io/travel-images/weekendList01.jpg",
        "title": "北京温泉排行榜",
        "desc": "细数北京温泉,温暖你的冬天"
      }, {
        "id": "0002",
        "imgUrl": "https://qdywxs.github.io/travel-images/weekendList02.jpg",
        "title": "北京必游TOP10",
        "desc": "来北京必去的景点非这些地方莫属"
      }, {
        "id": "0003",
        "imgUrl": "https://qdywxs.github.io/travel-images/weekendList03.jpg",
        "title": "寻找北京的皇城范儿",
        "desc": "数百年的宫廷庙宇,至今依旧威严霸气"
      }, {
        "id": "0004",
        "imgUrl": "https://qdywxs.github.io/travel-images/weekendList04.jpg",
        "title": "学生最爱的博物馆",
        "desc": "周末干嘛?北京很多博物馆已经免费开放啦"
      }, {
        "id": "0005",
        "imgUrl": "https://qdywxs.github.io/travel-images/weekendList05.jpg",
        "title": "儿童剧场,孩子的乐园",
        "desc": "带宝贝观看演出,近距离体验艺术的无穷魅力"
      }]
  }
}

2 获取并渲染数据

🔗前置知识:
《JavaScript 基础——JS 提供的对象:② 正则表达式》
《Vue 入门——② Vue 实例的生命周期函数》
《深入理解 Vue 组件——② 父子组件间的数据传递》

在我们的项目中,首页一共有 5 个小组件,而每个小组件都有自己的数据。即,每一个小组件中,都需要发送请求来获取数据。

❌但这就意味着,打开首页时,一次至少需要发送 5 个请求。如此一来,网页的性能就很低。

✅更为合理的方式就是:整个首页,只发送一个 AJAX 请求。

❓在哪里发送这个 AJAX 请求呢?
答:在首页 Home.vue 这个组件中发送 AJAX 请求、获取到数据,然后 Home.vue 将数据传递给其他的小组件。

2️⃣要在项目中使用 Axios,首先需要安装。

2️⃣-①:打开终端,在项目目录下运行命令 npm install --save axios 进行安装;

travel10-02.png

2️⃣-②:打开 home 下的 Home.vue ,在这里写 AJAX 请求的代码;

 <template>
  <div>
    <home-header></home-header>
    <home-swiper></home-swiper>
    <home-icons></home-icons>
    <home-recommend></home-recommend>
    <home-weekend></home-weekend>
  </div>

</template>

<script>
import HomeHeader from './components/Header'
import HomeSwiper from './components/Swiper'
import HomeIcons from './components/Icons'
import HomeRecommend from './components/Recommend'
import HomeWeekend from './components/Weekend'

import axios from 'axios' /* 2️⃣-③:引入 Axios; */

export default {
  name: 'Home',
  components: {
    HomeHeader,
    HomeSwiper,
    HomeIcons,
    HomeRecommend,
    HomeWeekend
  },
  methods: { /* 2️⃣-⑤:在 methods 中定义 getHomeInfo 方法; */

    getHomeInfo () { /* 2️⃣-⑥:请求获取 static 下 mock 中的 index.json 文件; */
      axios.get('/static/mock/index.json')

        .then(this.getHomeInfoSucc) /*
        														2️⃣-⑦:Axios 返回的结果是一个 Promise 对象,
        														所以当请求成功时,执行 getHomeInfoSucc 方法;
                                     */
    },
    getHomeInfoSucc (res) {
      console.log(res) /* 2️⃣-⑧:getHomeInfoSucc 方法打印出获取到的数据。 */
    }
  },
  mounted () { /* 2️⃣-④:在 mounted 中执行 getHomeInfo 这个获取首页数据的方法; */
    this.getHomeInfo()
  }
}
</script>

<style>
</style>

保存后,返回网页查看控制台,可以看到我们成功获取到了数据: travel10-03.png

❌但,我们的代码里存在一个“隐患”: travel10-04.png

我们请求的地址,是本地模拟数据的接口地址。如果项目要发布上线,就不能使用这个地址。也就是说,我们需要在上线前更改这部分的代码(比如,把地址替换为 /api/index.json 这种格式)。但上线之前更改代码,是有“风险”的,非常不建议这样做。

所以,我们可以在 Webpack 的“开发环境”配置中,进行一些配置:通过 proxy 这个代理功能,实现当在开发环境中请求 /api/index.json 时,转发到本地的 mock 文件夹下

3️⃣打开项目目录下的 config 中的 index.js 文件:

'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.

const path = require('path')

module.exports = {
  dev: {

    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',

    proxyTable: { /* 3️⃣-①:在开发环境的 proxyTable 中进行配置; */

      '/api': { /* 3️⃣-②:当请求 /api 这个目录时; */
        
        target: 'http://localhost:8080', /*
        																 3️⃣-③:希望它把请求目标,转发到当前服务器
        																 的 8080 端口上;
                                          */
        
        pathRewrite: { /* 3️⃣-④:但请求的路径要进行替换; */

          '^/api': '/static/mock' /*
          												3️⃣-⑤:当请求的地址是以 /api 为开头的,就把它
          												替换到本地的 static 中的 mock 文件夹下。
                                   */
        }
      }
    },

    // Various Dev Server settings
    host: 'localhost', // can be overwritten by process.env.HOST
    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
    autoOpenBrowser: false,
    errorOverlay: true,
    notifyOnErrors: true,
    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-

    // Use Eslint Loader?
    // If true, your code will be linted during bundling and
    // linting errors and warnings will be shown in the console.
    useEslint: true,
    // If true, eslint errors and warnings will also be shown in the error overlay
    // in the browser.
    showEslintErrorsInOverlay: false,

    /**
     * Source Maps
     */

    // https://webpack.js.org/configuration/devtool/#development
    devtool: 'cheap-module-eval-source-map',

    // If you have problems debugging vue-files in devtools,
    // set this to false - it *may* help
    // https://vue-loader.vuejs.org/en/options.html#cachebusting
    cacheBusting: true,

    cssSourceMap: true
  },

  build: {
    // Template for index.html
    index: path.resolve(__dirname, '../dist/index.html'),

    // Paths
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',

    /**
     * Source Maps
     */

    productionSourceMap: true,
    // https://webpack.js.org/configuration/devtool/#production
    devtool: '#source-map',

    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],

    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  }
}

3️⃣-⑥:打开 home 下的 Home.vue

<template>
  <div>
    <home-header></home-header>
    <home-swiper></home-swiper>
    <home-icons></home-icons>
    <home-recommend></home-recommend>
    <home-weekend></home-weekend>
  </div>

</template>

<script>
import HomeHeader from './components/Header'
import HomeSwiper from './components/Swiper'
import HomeIcons from './components/Icons'
import HomeRecommend from './components/Recommend'
import HomeWeekend from './components/Weekend'
import axios from 'axios'

export default {
  name: 'Home',
  components: {
    HomeHeader,
    HomeSwiper,
    HomeIcons,
    HomeRecommend,
    HomeWeekend
  },
  methods: {
    getHomeInfo () {
      axios.get('/api/index.json') /* ❗️将 /static/mock 替换为 /api。 */
        .then(this.getHomeInfoSucc)
    },
    getHomeInfoSucc (res) {
      console.log(res)
    }
  },
  mounted () {
    this.getHomeInfo()
  }
}
</script>

<style>
</style>

保存代码,因为更改了 Webpack 配置项,所以需要重启服务器后,再打开页面。可以看到,我们成功从 /api/index.json 路径获取到了数据: travel10-05.png

Home.vue 获取到数据后,就需要把数据传递给子组件。

我们回忆一下“父组件向子组件传值”的过程:

  1. 父组件的 data 中定义数据;
  2. 在父组件的模板中,通过“属性”给子组件传递数据;
  3. 子组件通过“props”接收数据。

4️⃣打开 home 下的 Home.vue

<template>
  <div>
    <home-header :city="city"></home-header> <!-- 4️⃣-②:通过属性 :city,把数据 city 传递
																						 给子组件 Header.vue; -->

    <home-swiper></home-swiper>
    <home-icons></home-icons>
    <home-recommend></home-recommend>
    <home-weekend></home-weekend>
  </div>

</template>

<script>
import HomeHeader from './components/Header'
import HomeSwiper from './components/Swiper'
import HomeIcons from './components/Icons'
import HomeRecommend from './components/Recommend'
import HomeWeekend from './components/Weekend'
import axios from 'axios'

export default {
  name: 'Home',
  components: {
    HomeHeader,
    HomeSwiper,
    HomeIcons,
    HomeRecommend,
    HomeWeekend
  },
  data () {
    return {
      city: '' /* 4️⃣-①:因为 city 里的数据是字符串,所以在 data 中初始化数据 city 为空字符串; */
    }
  },
  methods: {
    getHomeInfo () {
      axios.get('/api/index.json')
        .then(this.getHomeInfoSucc)
    },
    getHomeInfoSucc (res) {

      res = res.data /* 4️⃣-③:将获取到的数据中的 data 赋值给变量 res; */

      if (res.ret && res.data) { /*
      													 4️⃣-④:如果 res.ret 为 true(即,后端正确返回了结果),
      													 并且 res 中有对应的数据 data;
                                  */

        const data = res.data /* 4️⃣-⑤:为了方便,可以将 res.data 赋值给变量 data; */

        this.city = data.city /* 4️⃣-⑥:将数据项中的 city 赋值给 this.city; */
      }
    }
  },
  mounted () {
    this.getHomeInfo()
  }
}
</script>

<style>
</style>

4️⃣-⑦:打开 home 下 components 中的 Header.vue ,在子组件中接收数据;

<template>
  <div class="header">
    <div class="header-left">
      <span class="iconfont back-icon">&#xe658;</span>
    </div>
    <div class="header-input">
      <span class="iconfont">&#xe63c;</span>
      输入城市/景点/游玩主题
    </div>
    <div class="header-right">
      
      {{this.city}} <!-- 4️⃣-⑨:把“城市”替换为 this.city,用插值表达式渲染到页面上。 -->
      
      <span class="iconfont arrow-icon">&#xe65c;</span>
    </div>
  </div>
</template>

<script>
export default {
  name: 'HomeHeader',

  props: { /*
  				 4️⃣-⑧:在子组件新增 props,里边接收属性 :city 传递的数据项 city,city 的值必须
  				 为字符串;
            */
    city: String
  }
}
</script>

<style lang="stylus" scoped>
@import '~styles/varibles.styl'

.header
  display: flex
  line-height: .86rem
  color: #fff
  background: $bgColor
  .header-left
    float: left
    width: .64rem
    .back-icon
      display: block
      text-align: center
      font-size: .56rem
  .header-input
    flex: 1
    margin-top: .12rem
    margin-left: .2rem
    padding-left: .12rem
    height: .64rem
    line-height: .64rem
    color: #ccc
    background: #fff
    border-radius: .1rem
  .header-right
    float: right
    width: 1.24rem
    text-align: center
    .arrow-icon
      margin-left: -0.1rem
</style>

保存后,返回页面查看,右上角的文字原本是“城市”,现在被“北京”替换了: travel10-06.png

❓为什么会显示“北京”呢?
答:因为 AJAX 请求到的数据中, city 对应的是“北京”。然后,我们把 city 的值,赋值给了 data 函数里返回的 citytravel10-07.png

随后,通过属性 :city ,又将数据 city 传递给了子组件 Header.vuetravel10-08.png

子组件中接收了 citytravel10-09.png

所以,当 city 发生变化时,子组件中就会跟着变化。这也就是“父组件向子组件传递数据”的具体过程。

理解了这个具体过程,其他组件的数据,我们可以“依葫芦画瓢”来完成了。

5️⃣打开 home 下的 Home.vue

<template>
  <div>
    <home-header :city="city"></home-header>

    <home-swiper :list="swiperList"></home-swiper> <!-- 5️⃣-②:通过属性 list 把 swiperList
																									 传递给子组件 Swiper.vue; -->

    <home-icons></home-icons>
    <home-recommend></home-recommend>
    <home-weekend></home-weekend>
  </div>

</template>

<script>
import HomeHeader from './components/Header'
import HomeSwiper from './components/Swiper'
import HomeIcons from './components/Icons'
import HomeRecommend from './components/Recommend'
import HomeWeekend from './components/Weekend'
import axios from 'axios'

export default {
  name: 'Home',
  components: {
    HomeHeader,
    HomeSwiper,
    HomeIcons,
    HomeRecommend,
    HomeWeekend
  },
  data () {
    return {
      city: '',
      swiperList: [] /*
      							 5️⃣-①:因为 swiperList 里的数据是数组,所以初始化 swiperList 
      							 为空数组;
                      */
    }
  },
  methods: {
    getHomeInfo () {
      axios.get('/api/index.json')
        .then(this.getHomeInfoSucc)
    },
    getHomeInfoSucc (res) {
      res = res.data
      if (res.ret && res.data) {
        const data = res.data
        this.city = data.city

        this.swiperList = data.swiperList /*
        																	5️⃣-③:一旦获取到数据,就把数据中的 swiperList
        																	赋值给 this.swiperList;
                                           */
      }
    }
  },
  mounted () {
    this.getHomeInfo()
  }
}
</script>

<style>
</style>

5️⃣-④:打开 home 下 components 中的 Swiper.vue

<template>
  <div class="wrapper">
    <swiper :options="swiperOption">

      <swiper-slide v-for="item of list" :key="item.id"> <!-- 5️⃣-⑦:循环的数组改为
																												 接收到的数据 list。 -->

        <img class="swiper-img" :src="item.imgUrl">
      </swiper-slide>
      <div class="swiper-pagination"  slot="pagination"></div>
    </swiper>
  </div>
</template>

<script>
export default {
  name: 'HomeSwiper',

  props: { /* 5️⃣-⑥:在 props 中接收父组件通过 :list 属性传递过来的数据,它对应的值为数组; */
    list: Array
  },
  
  data () { /* 5️⃣-⑤:删除 data 中的 swiperList; */
    return {
      swiperOption: {
        pagination: '.swiper-pagination',
        loop: true,
        autoplay: 3000
      }
    }
  }
}
</script>

<style lang="stylus" scoped>
.wrapper >>> .swiper-pagination-bullet-active
  background: #fff
.wrapper
  overflow: hidden
  width: 100%
  height: 0
  padding-bottom: 31.25%
  background: #eee
  .swiper-img
    width: 100%
</style>

❌保存后,返回页面查看效果,轮播里的图片正常渲染了。但,轮播中默认显示的第一张图片,实际是数据中的第四张图片:

travel_10-10.gif

❓为什么刷新页面后,轮播的第一张图片显示的是数据里的最后一张轮播图片?
答:因为当我们在 Swiper.vue 中使用第三方插件的 <swiper> 时,页面还没有获取 AJAX 数据。这时 props 接收的数据 listHome.vue 中定义的 “swiperList 空数组”。

当 AJAX 获取数据完成后,swiperList 才变成真正的数据项。此时,再传递数据项给 Swiper.vue 时,它才获取到新的数据,重新渲染了新数据中对应的图片。

即, <swiper> 最初创建时,是通过空数组创建的。这导致了刷新页面后,轮播的第一张图片显示的是数据里的最后一张轮播图。

❓如何解决这个问题呢?
答:让 <swiper> 初次创建时,由完整的数据创建,而不是由空数组创建。

6️⃣打开 home 下的 Swiper.vue

<template>
  <div class="wrapper">

    <!-- 6️⃣-③:在 swiper 标签上添加 v-if 指令,使用计算属性 showSwiper。 -->
    <swiper :options="swiperOption" v-if="showSwiper">

      <swiper-slide v-for="item of list" :key="item.id">
        <img class="swiper-img" :src="item.imgUrl">
      </swiper-slide>
      <div class="swiper-pagination"  slot="pagination"></div>
    </swiper>
  </div>
</template>

<script>
export default {
  name: 'HomeSwiper',
  props: {
    list: Array
  },
  data () {
    return {
      swiperOption: {
        pagination: '.swiper-pagination',
        loop: true,
        autoplay: 3000
      }
    }
  },
  computed: { /* 6️⃣-①:在 computed 中定义一个 showSwiper 计算属性; */
    
    showSwiper () {
      return this.list.length /*
      												6️⃣-②:showSwiper 返回 this.list.length (即,当 list
      												是空数组时, list.length 为 false,轮播就不会被创建;当数据
                              获取到之后, list.length 变为 true,轮播才被创建)。
                               */
    }
  }
}
</script>

<style lang="stylus" scoped>
.wrapper >>> .swiper-pagination-bullet-active
  background: #fff
.wrapper
  overflow: hidden
  width: 100%
  height: 0
  padding-bottom: 31.25%
  background: #eee
  .swiper-img
    width: 100%
</style>

保存后,返回页面查看。当页面刷新后,轮播正确显示数据中的第一张图片:

travel_10-11.gif

7️⃣接下来,传递数据给 Icons.vue 小组件。打开 home 下的 Home.vue

<template>
  <div>
    <home-header :city="city"></home-header>
    <home-swiper :list="swiperList"></home-swiper>

    <home-icons :list="iconList"></home-icons> <!-- 7️⃣-②:把数据 iconList 通过属性 list 
																							 传递给子组件 Icons.vue; -->

    <home-recommend></home-recommend>
    <home-weekend></home-weekend>
  </div>

</template>

<script>
import HomeHeader from './components/Header'
import HomeSwiper from './components/Swiper'
import HomeIcons from './components/Icons'
import HomeRecommend from './components/Recommend'
import HomeWeekend from './components/Weekend'
import axios from 'axios'

export default {
  name: 'Home',
  components: {
    HomeHeader,
    HomeSwiper,
    HomeIcons,
    HomeRecommend,
    HomeWeekend
  },
  data () {
    return {
      city: '',
      swiperList: [],
      
      iconList: [] /* 7️⃣-①:初始化 iconList 为空数组; */
    }
  },
  methods: {
    getHomeInfo () {
      axios.get('/api/index.json')
        .then(this.getHomeInfoSucc)
    },
    getHomeInfoSucc (res) {
      res = res.data
      if (res.ret && res.data) {
        const data = res.data
        this.city = data.city
        this.swiperList = data.swiperList

        this.iconList = data.iconList /*
        															7️⃣-③:一旦获取到数据,就把 data.iconList 赋值
        															给 this.iconList;
                                       */
      }
    }
  },
  mounted () {
    this.getHomeInfo()
  }
}
</script>

<style>
</style>

7️⃣-④:打开 home 下 components 中的 Icons.vue 接收数据;

<template>
  <div class="icons">
    <swiper :options="swiperOption">
      <swiper-slide v-for="(page, index) of pages" :key="index">
        <div class="icon" v-for="item of page" :key="item.id">
          <div class="icon-img">
            <img class="icon-img-content" :src="item.imgUrl">
          </div>
          <p class="icon-desc">{{item.desc}}</p>
        </div>
      </swiper-slide>
    </swiper>
  </div>
</template>

<script>
export default {
  name: 'HomeIcons',

  props: { /* 7️⃣-⑥:在 props 中接收父组件通过属性 :list 传递过来的数据,它对应的值为数组; */
    list: Array
  },
  data () { /* 7️⃣-⑤:删除 data 中的 iconList 数据; */
    return {
      swiperOption: {
        autoplay: false
      }
    }
  },
  computed: {
    pages () {
      const pages = []

      /* 7️⃣-⑦:计算属性中循环的数组,改为接收到的数据 list。 */
      this.list.forEach((item, index) => {
        const page = Math.floor(index / 8)
        if (!pages[page]) {
          pages[page] = []
        }
        pages[page].push(item)
      })
      return pages
    }
  }
}
</script>

<style lang="stylus" scoped>
@import '~styles/varibles.styl'
@import '~styles/mixins.styl'

.icons >>> .swiper-container
  height: 0
  padding-bottom: 50%
.icons
  margin-top: .1rem
  .icon
    position: relative
    overflow: hidden
    float: left
    width: 25%
    height: 0
    padding-bottom: 25%
    .icon-img
      position: absolute
      top: 0
      left: 0
      right: 0
      bottom: .44rem
      box-sizing: border-box
      padding: .1rem
      .icon-img-content
        display: block
        margin: 0 auto
        height: 100%
    .icon-desc
      position: absolute
      left: 0
      right: 0
      bottom: 0
      height: .44rem
      line-height: .44rem
      text-align: center
      color: $darkTextColor
      ellipsis()
</style>

保存后,返回页面查看:

travel_10-12.gif

8️⃣传递数据给“热销推荐”组件和“周末去哪儿”组件,打开 home 下的 Home.vue

<template>
  <div>
    <home-header :city="city"></home-header>
    <home-swiper :list="swiperList"></home-swiper>
    <home-icons :list="iconList"></home-icons>

    <home-recommend :list="recommendList"></home-recommend> <!-- 8️⃣-②:通过属性 list,
																														把 recommendList 传递给
																														子组件 Recommend.vue; -->

    <home-weekend :list="weekendList"></home-weekend> <!-- 8️⃣-③:通过属性 list,把
																											weekendList 传递给 Weekend.vue;
																											-->
  </div>

</template>

<script>
import HomeHeader from './components/Header'
import HomeSwiper from './components/Swiper'
import HomeIcons from './components/Icons'
import HomeRecommend from './components/Recommend'
import HomeWeekend from './components/Weekend'
import axios from 'axios'

export default {
  name: 'Home',
  components: {
    HomeHeader,
    HomeSwiper,
    HomeIcons,
    HomeRecommend,
    HomeWeekend
  },
  data () {
    return {
      city: '',
      swiperList: [],
      iconList: [],

      recommendList: [], /* 8️⃣-①:初始化 recommendList 和 weekendList 为空数组; */
      weekendList: []
    }
  },
  methods: {
    getHomeInfo () {
      axios.get('/api/index.json')
        .then(this.getHomeInfoSucc)
    },
    getHomeInfoSucc (res) {
      res = res.data
      if (res.ret && res.data) {
        const data = res.data
        this.city = data.city
        this.swiperList = data.swiperList
        this.iconList = data.iconList
			
        /* 8️⃣-④:一旦获取到数据,就把 data.recommendList 赋值给 this.recommendList; */
        this.recommendList = data.recommendList

        /* 8️⃣-⑤:一旦获取到数据,就把 data.weekendList 赋值给 this.weekendList; */
        this.weekendList = data.weekendList
      }
    }
  },
  mounted () {
    this.getHomeInfo()
  }
}
</script>

<style>
</style>

8️⃣-⑥:打开 home 下 components 中的 Recommend.vue ,在“热销推荐”组件接收数据;

<template>
  <div>
     <div class="title">热销推荐</div>
     <ul>

      <!-- 8️⃣-⑧:li 标签循环的数据,变为传递进来的 list; -->
       <li class="item border-bottom" v-for="item of list" :key="item.id">

         <img class="item-img" :src="item.imgUrl">
         <div class="item-info">
           <p class="item-title">{{item.title}}</p>
           <p class="item-desc">{{item.desc}}</p>
           <button class="item-button">查看详情</button>
         </div>
       </li>
     </ul>
  </div>
</template>

<script>
export default {
  name: 'HomeRecommend',
  props: { /* 8️⃣-⑦:在 props 中接收父组件通过属性 :list 传递过来的数据,它对应的值为数组; */
    list: Array
  }
  /* ❗️Recommend.vue 中的 data 只有 recommendList 一个数据,所以删除 data。 */
}
</script>

<style lang="stylus" scoped>
@import '~styles/mixins.styl'
.title
  margin-top: .2rem
  background: #eee
  line-height: .8rem
  text-indent: .2rem
.item
  overflow: hidden
  display: flex
  height: 1.9rem
  .item-img
    width: 1.7rem
    height: 1.7rem
    padding: .1rem
  .item-info
    flex: 1
    padding: .1rem
    min-width: 0
    .item-title
      line-height: .54rem
      font-size: .32rem
      ellipsis()
    .item-desc
      line-height: .4rem
      color: #ccc
      ellipsis()
    .item-button
      margin-top: .16rem
      line-height: .44rem
      color: #fff
      background: #ff9300
      padding: 0 .2rem
      border-radius: .06rem
</style>

8️⃣-⑨:打开 home 下 components 中的 Weekend.vue ,在“周末去哪儿”组件接收数据;

<template>
  <div>
    <div class="title">周末去哪儿</div>
    <ul>

      <!-- 8️⃣-⑪:li 标签循环的数据,变为传递进来的 list。 -->
      <li class="item border-bottom" v-for="item of list" :key="item.id">

        <div class="item-img-wrapper">
          <img class="item-img" :src="item.imgUrl">
        </div>
        <div class="item-info">
          <p class="item-title">{{item.title}}</p>
          <p class="item-desc">{{item.desc}}</p>
        </div>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'HomeWeekend',
  props: { /* 8️⃣-⑩:在 props 中接收父组件通过属性 :list 传递过来的数据,它对应的值为数组; */
    list: Array
  }
  /* ❗️Weekend.vue 中的 data 只有 weekendList 一个数据,所以删除 data。 */
}
</script>

<style lang="stylus" scoped>
@import '~styles/mixins.styl'
.title
  background: #eee
  line-height: .8rem
  text-indent: .2rem
.item-img-wrapper
  overflow: hidden
  height: 0
  padding-bottom: 38%
  .item-img
    width: 100%
.item-info
  padding: .1rem
  .item-title
    line-height: .54rem
    font-size: .32rem
    ellipsis()
  .item-desc
    line-height: .4rem
    color: #ccc
    ellipsis()
</style>

保存后,返回页面查看效果:

travel_10-13.gif

以上,我们就完成了首页开发。

🏆本篇总结:

  • 在首页 Home.vue 使用 Axios 发送了一次 AJAX 请求,动态获取到首页所需的所有数据;
  • 然后,通过“父组件向子组件间传值”,把数据分别传递给子组件;
  • 最终,使数据正确渲染到页面上。

祝好,qdywxs ♥ you!