Vue高仿饿了么项目(4)—header组件开发

目标

完成header组件需要完成以下几部分

  1. 商家信息的展示—获取数据
  2. 公告信息展示—外部组件的开发
  3. 弹出层的实现

获取数据

1.安装vue-resource,在main.js里注册此插件
Vue.js有一个插件vue-resource,用来解决前后端数据请求,数据交互的。
安装方法如下

npm install vue-resource --save

安装成功后引用并注册

//引用
import VueResource from 'vue-resource'
//全局注册
Vue.use(VueResource)

vue-resource的使用例子

{
  // GET /someUrl
  this.$http.get('/someUrl').then(response => {

    // get body data
    this.someData = response.body;

  }, response => {
    // error callback
  });
}

这里的this就是vue的实例,使用vue-resource相当于给每一个vue实例去扩展了一个.$http的方法,这样就可以在组件里面通过this.$http访问到。

2.在App.vue中定义一个seller对象,使用data方法,在函数内返回一个空对象
header组件有很多关于商家的一些数据,这些数据的来源是通过异步请求后端的数据接口而获得的,header组件负责接收这样的数据并渲染。可以通过header的父组件即App.vue来发送一个ajax请求,然后通过header的一个prop属性传递给这个组件。首先在App.vue定义一个seller的对象,用data ()方法,在Vue.js中,规定data是一个函数,因为组件是可以复用的,如果定义成一个对象修改某一个组件会影响另外一个组件,所以这里要定义成一个函数。函数里面要返回一个seller空对象,然后通过发送一个Ajax请求拿到一个seller对象放到这个空对象里面,这样就拿到了这个对象的数据。

export default {
    data () {
        return {
            seller: {}
        }
    }
}

3.在App.vue中通过vue-resource发送一个Ajax请求,获取后台的数据(seller对象),然后塞给上一步的空对象

  • 什么时候去发送这样一个Ajax请求?每个Vue实例在实例化的时候经过一系列的过程,例如需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。生命周期其中有一个钩子叫做created (),它也是一个方法,vue实例被生成后就调用这个函数。一般可以在created ()函数中调用ajax获取页面初始化所需的数据。一个vue实例被生成后还要绑定到某个html元素上,之后还要进行编译,然后再插入到document中。每一个阶段都会有一个钩子函数,方便开发者在不同阶段处理不同逻辑。
  • created () 里面通过 .get () 方法去获取数据,获取地址是“ /api/seller ”,请求发送以后,调用 .then () 方法。.then () 方法中的第一个参数是成功的回调,第二个参数是失败的回调。因为这里是模拟后台数据,所以一定是成功的,所以只写成功的回调方法。response 是一个方法,返回的内容不是json对象,而是属性。看文档可以知道,body参数返回的数据类型是 object 类型,正是我们所需要的。
  • 3)接着判断 errno 是否为 0,为 0 表示正常,然后返回数据。关于 errno 的设置在 webpack.dev.conf.js 中可以找到。这里定义一个常量 ERR_OK 为 0,是为了阅读代码时更加清晰,知道 0 代表什么,无需通篇查找 errno = 0 的含义,代码更为友好。
export default {
  created () {
    this.$http.get('/api/seller').then((response) => {
      response = response.body
      console.log(response)
      if (response.errno === ERR_OK) {
        this.seller = response.data
        console.log(this.seller)
      }
    })
  }

接下来需要将seller对象传递给header组件,让header组件将其渲染出来。可以通过v-bind:将其进行数据绑定,v-bind的缩写为:。首先是seller对象的传递

:seller="seller">

然后在header.vue中通过props属性接收App.vue中传递过来的seller对象

export default {
  props: {
    seller: {
      type: Object
    }
  }
}

接下来进行外层组件和弹出层的设计。代码框架:



外部组件

1.上层部分。

<div class="content-wrapper">
   <div class="avatar">
     <img width="64" height="64" :src="seller.avatar">
   div>
   <div class="content">
     <div class="title">
       <span class="brand">span>
       <span class="name">{{seller.name}}span>
     div>
     <div class="description">
       {{seller.description}}/{{seller.deliveryTime}}分钟送达
     div>
     <div v-if="seller.supports" class="support">
       <span class="icon" :class="classMap[seller.supports[0].type]">span>
       <span class="text">{{seller.supports[0].description}}span>
     div>
   div>
   <div v-if="seller.supports" class="support-count">
     <span class="count">{{seller.supports.length}}span>
     <i class="icon-keyboard_arrow_right">i>
   div>
div>

1) 引用头像图片地址,之所以用:src而不是src的原因是seller.avatar在一开始是不存在的,最开始seller是一个空对象,它的数据内容是通过发送Ajax异步请求来获取的。所以直接用src的话,编译就会直接解析src,此时的seller是一个空对象,自然就找不到seller.avatar,故报错。

2) 之所以使用v-if来判断是否存在supports是因为这是由数据决定的,有可能该数组不存在。

3) 如果不加v-if也能够正确显示信息,但是控制台会报错。这是因为在 App.vue 的 html 代码中,我们将 seller 对象传递给了 header 组件,而在下边代码中, seller 对象一开始被设置为空对象,其数据是通过 ajax 请求异步获取数据然后填充得来的。所以此时 header 组件接收的只是一个空的 seller 对象,本来就是 undefined 。编译器此时解析的话,自然找不到 seller 对象的相关数据,自然就报错了。

4) support的设置

"supports": [
   {
     "type": 0,
     "description": "在线支付满28减5"
   },
   {
     "type": 1,
     "description": "VC无限橙果汁全场8折"
   },
   {
     "type": 2,
     "description": "单人精彩套餐"
   },
   {
     "type": 3,
     "description": "该商家支持发票,请下单写好发票抬头"
   },
   {
     "type": 4,
     "description": "已加入“外卖保”计划,食品安全保障"
   }
 ]

support的设置需要根据后端返回的数据,进行判断后动态的显示,根据显示内容的不同,加载对应的图标不同,有可能type为0时排在第一位,可能type为其他数值时排第一位。在data.json和supports数组中只有type和description属性,如何取得正确的图片?
思路:每一个type对应前端中不同的class,然后给不同的class添加不同的图片就可以了。所以可以给support动态绑定一个class,即根据后端返回的type(第一位),确定到底绑定哪一个class,然后将该class传递给动态绑定的class(:class),这样能正确显示。

如何实现:

第一步:在组件 created() 中,定义一个 classMap,将 typeclass 进行绑定(在 data.json 文件中,supports 数组中 type 顺序:减,折,套餐,发票,保障,则对应class名称:decrease,discount,special,invoice,guarantee);

第二步:给不同的 class 设置不同的图片;

第三步:将数组 classMap 绑定到 class 上,即 :class="classMap[seller.supports[0].type]

2.公告栏部分
点击效果的实现:
各元素添加点击事件v-on:click=”showDetail”,在method()方法里定义点击事件。
detailShow是弹出层设置的用于控制弹出层显示与隐藏的一个变量,detailShow值为true时显示,为false时隐藏。

3.弹出层的实现
Vue高仿饿了么项目(4)—header组件开发_第1张图片
当浮层高度大于手机时,应该有下拉功能。不管浮层中内容有多少,关闭按钮都必须固定位于下方,这里用到了css sticky footer布局来实现该功能。该布局一般会有detail-wrapper和detail-close两个层,而真正的内容写在detail-main中:

<div class="detail-wrapper clearfix">
  <div class="detail-main">div>
div>
<div class="detail-close">div>

下面将首先对弹出层的代码框架布局详细设计,然后把弹出层分成为弹出层内容和弹出层关闭按钮两部分进行详细设计。

3.1 代码框架布局
1)显示隐藏设置
弹出层是应当实现显示和隐藏功能,这里可以用vue的条件渲染v-show来实现。设置一个detailShow变量,通过改变这个变量的值(true/false),来控制弹出层的显示与隐藏。
实现:给vue实例中添加一个选项data(),这个data()是一个函数,该方法会返回一个Object,Object里面的变量就是它所需要去跟踪依赖的一些变量。也就是说,vue在实例化时,会对data中的对象去进行遍历,然后添加getter和setter方法,这样,当变量变化时,DOM会根据变量的变化而变化,所以这里用v-show来控制detailShow。对于频繁的切换用v-show比较好,对于运行时条件很少改变的时候用v-if较好。使用v-show的好处就是不需要额外写DOM代码,都是直接操作变量即可。

data () {
    return {
      detailShow: false
    }
  },
  methods: {
    showDetail () {
      this.detailShow = true
    },
    hideDetail () {
      this.detailShow = false
    }
  }

代码中有两处需要设计detailShow的设置,为这两个层添加click事件。它的原理就是在方法里面改变detailShow的值,因为detailShow的依赖跟踪的,当点击之后调用该方法,this.detailShow会跟踪依赖到data里面的detailShow,并改变其值,由于它的依赖跟踪,vue可以检测到其变化,并将该变化映射到DOM上,这样就可以通过改变数据的值来实现弹层的悬浮或隐藏。
2)过渡渐变(过渡渐变)
弹出层弹出的时候,背景颜色应该是由浅到深,弹出层隐藏的时候,背景颜色应该是由深到浅。这种渐变的设置可以用vue的内置组件transition来实现。以下是HTML的基本结构:

<transition name="fade">
    <div x-show="detailShow" class="detail">div>
transition>

transition有一个name属性,添加该属性后,会自动生成CSS过渡类名。例如:name=”fade”将自动扩展为fade-enter,fade-enter-active等。在这里设置背景颜色后,在设置过渡的类名:fade-enter,fade-enter-active,fade-leave-to,fade-leave-active的样式即可。

.detail
      position: fixed
      z-index: 100
      top: 0
      left: 0
      width: 100%
      height: 100%
      overflow: auto
      backdrop-filter: blur(10px)
      opacity: 1
      background: rgba(7, 17, 27, 0.8)
      &.fade-enter-active, &.fade-leave-active
        transition: all 0.5s
      &.fade-enter, &.fade-leave-active
        opacity: 0
        background: rgba(7, 17, 27, 0)

3)css的设置

  • 弹出层相对于窗口应该是fixed结构
  • 弹出层应该位于所有层最上方,所以父元素设置z-index为100
  • 当内容超过屏幕高度时,弹出层应该产生滚动条,所以设置overflow属性
  • 背景文字虚化设置backdrop-filter.blur(10px),这个设置支持ios设备,可以在ios手机上查看效果。

3.2 弹出层内容
1)css设置

  • 要给detai-wrapper层加上清除浮动clearfix,样式设置写在base.styl
  • .detail-wrapper的最小高度应该跟视口高度一样,所以设置为100%
  • .detail-main中设置padding-bottom 64px。如果没有padding-bottom,.detail-main的内容会盖住关闭按钮。加了后会撑开高度,这部分高度会给按钮留出位置。

2)星级组件设置(star.vue)
星级评价这一块内容在之后多个页面会使用到没所以将其抽象成star组件。
在这里设置为总共五颗星,评分范围为0-5,评分规则是向下取0.5倍数的值,比如4到4.4会变成4(显示四颗全星,一颗无星),4.5到4.9会变成4.5(显示四颗全星和一颗半星)。此处有一个思路,就是根据不同分数范围制作不同的图片,然后引用图片,如下图。
Vue高仿饿了么项目(4)—header组件开发_第2张图片
但是为了增加代码的可用性,最好不要这样做,可以通过分数来计算到底有多少颗全星和无星,以及是否有半星。
创建star.vue如下
2-1)HTML代码

<template>
   
class="star" :class="starType"> for="itemClass in itemClasses" :class="itemClass" class="star-item">
template>

先给星级组件添加父元素,类名是star,将星级组件包裹起来,并v-bind方式定义一个starType。再添加评星,用v-for指令遍历itemClasses数组,这个数组存放着的是所有星星存放的数组,遍历该数组,得到不同的itemClass,再通过CSS设置不同的itemClass,最终就可以得到不同的星级评定。
2-2)JavaScript代码

<script type="text/ecmascript-6">
const LENGTH = 5
const CLS_ON = 'on'
const CLS_HALF = 'half'
const CLS_OFF = 'off'
export default {
  props: {
    size: {
      type: Number
    },
    score: {
      type: Number
    }
  },
  computed: {
    starType () {
      return 'star-' + this.size
    },
    itemClasses () {
      let result = []
      let score = Math.floor(this.score * 2) / 2
      let hasDecimal = score % 1 !== 0
      let integer = Math.floor(score)
      for (let i = 0; i < integer; i++) {
        result.push(CLS_ON)
      }
      if (hasDecimal) {
        result.push(CLS_HALF)
      }
      while (result.length < LENGTH) {
        result.push(CLS_OFF)
      }
      return result
    }
  }
}
script>

2-2-1)先通过props去接收外部传来的两个参数:size和score,这两个参数都是Number类型。size的值是24,36,48中的一个,是图片的尺寸类型,score是评分。
2-2-2)starType是通过size参数计算来的,可以通过computed计算属性计算出来,因为参数size本身会有getter和setter方法依赖跟踪。通过计算属性拼接出starType的值,从而产生不同的starType,即不同的class,接下来对class(star-48,star-36,star-24)进行样式设计即可。其中,on、half、off分别代表全星、半星、无星。
2-2-3)itemClasses是数组,要根据score进行计算,数组的内容就是on、half、off的组合,通过这些类组成星级评定,比如三颗半星,那么itemClasses应该是有三个on,一个half和一个off,依旧在computed计算属性里写代码,因为itemClasses是数组,所以先定义一个result数组,然后进行score的计算转化。在转换后的score的基础上,对其求余,有余数代表有一颗半星,对score进行调整,该整数就是全星(on)的个数。注意:半星最多只有一个。最后就是将on和half写进result数组,判断result长度是否为5,不为5的话,要补上off。
2-2-4)在header.vue中通过components注册star组件并引用。

:size="48" :score="seller.score">
components: {
      star
    }

3)小标题的设置
小标题的设置,从样式上看,应该是左右两边的线有一个自适应的能力,可根据屏幕大小延伸,标题居中,标题与线之间还有一部分空白。此处用到了一个经典布局,即 flex 布局 。下面是 HTML代码,从代码中可以看到,这里用了三个 div 元素而不是 span 元素,这是因为用 span 的话,在一些 Android 浏览器上会产生一些间距问题,因此这里用 div 。

<div class="title">
   <div class="line">div>
   <div class="text">优惠信息div>
   <div class="line">div>
div>

以下是部分 CSS 代码,从代码中可以看到我们只写了一句 display flex,并没有写其它兼容性代码。这是因为编译时,vue-loader 中有 postcss 工具,可以自动添加有兼容性代码样式。postcss 是根据 can i use 官网写的代码。

.title
   display flex
   width 80%
   margin 28px auto 24px auto
   .line
     flex 1
     position relative
     top -6px
     border-bottom 1px solid rgba(255, 255, 255, 0.2)
   .text
     padding 0 12px
     font-weight 700
     font-size 14px

4)优惠信息的设置
Vue高仿饿了么项目(4)—header组件开发_第3张图片
使用无序列表元素 ul 和 li,用 v-for 遍历 data.json 文件中的 supports 数组,然后展示出来。因为 v-for 支持一个可选的第二个参数为当前项的索引,所以我们写大量额外的代码,只需要在 v-for 遍历时,加上 index,下边就可以可以很容易的取到正确的图片和文字了(classMap是五个图片类名的数组)。

<ul v-if="seller.supports" class="supports">
  <li class="support-item" v-for="(item, index) in seller.supports">
    <span class="icon" :class="classMap[seller.supports[index].type]">span>
    <span class="text">{{seller.supports[index].description}}span>
  li>
ul>

3.关闭按钮
1)CSS设置
margin -64px auto 0 auto:因为 .detail-wrapper 与 .detail-close 同级,而当 .detail-wrapper 满屏时,.detail-close 是不会抢占底部的空间,所以需要设置一个向上的margin,这样才能一直出现在屏幕的最底部。
2)添加关闭按钮功能
在 HTML 代码中加上 click 事件,注意 v-on:click 可以缩写为 @click

<div class="detail-close" v-on:click="hideDetail">
   class="icon-close">
div>

在 JavaScript 代码中将 detailShow 的值设置为 false(弹出层隐藏)。

methods: {
   hideDetail() {
     this.detailShow = false;
   }
 }

至此,header组件已经全部完成。

相关知识

  1. vue-router
  2. 关于router-go的改变
  3. 关于 v-link-active 的替换
  4. 关于 linkActiveClass 的设置
  5. 设备像素比相关知识

你可能感兴趣的:(vue高仿饿了么)