Vue.js仿eleme项目(3)

六,header组件开发

1. vue-resource

vue-resource 处理前后端请求数据交互。
安装引入注册同vue-router,都是第三方插件。

main方法中使用的是全局配置文件,所以在这里可以配置所有的公共数据。在VUE中规定data是一个函数。vue-resource 要将请求成功的数据转换为json格式,要调用 .body 这个属性,如果用json方法返回的是promise对象。created 是 Vue 生命周期的钩子函数,会在实例化的过程中自动调用的。

//App.vue
...
created() {
      // 这里只写成功的方法
      this.$http.get('api/seller').then((response) => {
        response = response.body;
        if (response.errno === ERR_OK) {
          this.seller = response.data;
          console.log(this.seller);
        }
      });
    },
...

2. 外部组件

编写header代码,向header中传ajax获取的seller。

<v-header :seller="seller"></v-header>

我们数据是异步得到的,最开始渲染dom的时候,seller为空对象。加上v-if是因为这里层次嵌套深,如果 seller 是 {},那么 seller.supports 为 undefined,那么 undefined[0] 就报错了。比如你需要访问3级数据比如 a.b.c 需要判断 v-if a.b否则会报错。

几个不错的QA:

Q: 感觉如果从子组件中的mounted钩子中发送ajax然后拿到数据渲染,也就不用写v-if判断了啊?
A: 有些情况,如果是想保持组件的纯净,只负责渲染,数据通过 props 的传入,对组件的复用性也会更好。有些情况,如果组件内部有一些交互逻辑需要组件闭环的话,确实可以把 ajax 请求写在组件内部。

header.vue中的props部分相当于要给header组件传入的参数,对应于App的template中的:seller传入。

为了消除inline-block由于空白字符产生的空隙,给父元素设置font-size=0。但是在bulletin中为了显示省略号不能用font-size的方式,就直接删除html中span之间的空格好了。

white-space:nowrap;overflow: hidden;text-overflow: ellipsis;这三个属性组合起来就是单行显示,多余文字省略号表示了

bulletin 的垂直对齐可以用我自己的方法设置title图片middle,或者用老师的方法先全部top对齐然后调整title图片的margin-top。

引入不同dpr的图片,在mixin.styl中定义这样的函数。

background的模糊效果图片背景通过绝对定位+filter实现。注意由于filter的blur效果header底部有溢出的模糊,所以要在header中添加overflow样式。

bg-image($url)
  background-image: url($url + "@2x.png")
  @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3)
    background-image: url($url + "@3x.png")

品牌图片在页面中被webpack打包成了base64地址。

关于不用rem em而用px的解释,http://coding.imooc.com/learn/questiondetail/3357.html

supports栏的书写要用class map实现。展示的是supports[0],但是type是动态的。supports的text中font-size为10px,在chrome下看不出来因为chrome下最小是12px。但是手机端看得出来。

垂直居中对齐问题用vertical-align: top 和调整line-height解决。

header.vue代码如下

<template>
  <div class="header">
    <div class="content-wrapper">
      <div class="avatar">
        ![](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="supports">
          <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>
    <div class="bulletin-wrapper">
      <span class="bulletin-title"></span><span class="bulletin-text">{{seller.bulletin}}</span>
      <i class="icon-keyboard_arrow_right"></i>
    </div>
    <div class="background">
      ![](seller.avatar)
    </div>
  </div>
</template>

<script type="text/ecmascript-6">
  export default {
    props: {
      seller: {
        type: Object
      }
    },
    created() {
      this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
    }
  };
</script>

<style lang="stylus" rel="stylesheet/stylus">
  @import "../../common/stylus/mixin"

  .header
    position: relative
    overflow: hidden
    color: white
    background-color: rgba(7, 17, 27, 0.5)
    .content-wrapper
      padding: 24px 12px 18px 24px
      position: relative
      font-size: 0
      .avatar
        display: inline-block
        vertical-align: top
        img
          border-radius: 2px
      .content
        display: inline-block
        margin-left: 16px
        .title
          margin: 2px 0 8px 0
          .brand
            display: inline-block
            vertical-align: top
            width: 30px
            height: 18px
            bg-image(brand)
            background-size: 30px 18px
            background-repeat: no-repeat
          .name
            margin-left: 16px
            font-size: 16px
            line-height: 18px;
            font-weight: bold
        .description
          margin-bottom: 10px
          line-height: 12px
          font-size: 12px
        .supports
          .icon
            display: inline-block
            vertical-align: top
            width: 12px
            height: 12px
            margin-right: 4px
            background-size: 12px 12px
            background-repeat: no-repeat
            &.decrease
              bg-image(decrease_1)
            &.discount
              bg-image(discount_1)
            &.guarantee
              bg-image(guarantee_1)
            &.invoice
              bg-image(invoice_1)
            &.special
              bg-image(special_1)
          .text
            line-height: 12px
            font-size: 10px

      .support-count
        position: absolute
        right: 12px
        bottom: 14px
        padding: 0 8px
        height: 24px
        line-height: 24px
        border-radius: 14px
        background: rgba(0, 0, 0, 0.2)
        text-align: center
        .count
          vertical-align: top
          font-size: 10px
        .icon-keyboard_arrow_right
          line-height: 24px
          margin-left: 2px
          font-size: 10px

    .bulletin-wrapper
      position: relative
      height: 28px
      line-height: 28px
      padding: 0 22px 0 12px
      white-space: nowrap
      overflow: hidden
      text-overflow: ellipsis
      background-color: rgba(7, 17, 27, 0.2)
      .bulletin-title
        display: inline-block
        vertical-align: middle
        width: 22px
        height: 12px
        bg-image(bulletin)
        background-size: 22px 12px
        background-repeat: no-repeat
      .bulletin-text
        margin: 0 4px
        font-size: 10px
      .icon-keyboard_arrow_right
        position: absolute
        right: 12px
        bottom: 8px
        font-size: 10px
    .background
      position: absolute
      top: 0px
      left: 0px
      width: 100%
      height: 100%
      z-index: -1
      filter: blur(10px)
</style>

3. 详情弹层页

这一部分也可以单独写一个组件,这里我们继续在header中编写代码。

3.1. 实现弹出层

弹出层是一个叫detail的div。用fixed布局。在对应的地方添加@click函数,并写入data和methods的vue方法控制点击出现事件,不需要编写改变dom事件,vue非常方便。

<div v-if="seller.supports" class="support-count" @click="showDetail">

    data() {
      return {
        detailShow: false
      };
    },
    methods: {
      showDetail() {
       this.detailShow = true;
      }
    },
    .detail
      position: fixed
      z-index: 100
      top: 0
      left: 0
      width: 100%
      height: 100%
      overflow: auto
      background-color: rgba(7, 17, 27, 0.8)

关于vue的new vue, export default语法的问题http://coding.imooc.com/learn/questiondetail/15435.html

Q: data 与 created 里定义变量的区别?
A: 如果你想为这个属性添加 getter 和 setter,就放在 data 里,data数据是响应的。如果不需要就直接挂在当前实例下。

3.2. CSS Sticky Footer

CSS秘密花园有文章介绍sticky footer。www.w3cplus.com/css3/css-secrets/sticky-footers.html

这里我们用一个兼容性较好的套路去解决sticky footer。

detail-main中的padding-bottom是必须的,本例子中用margin-bottom也可以。然后关闭icon部分用margin。这里关键点三个: 一、wrapper占据最小100%尺寸,此时footer层被挤到屏幕外。 二、(当内容不满一屏时,)footer层负位移从屏幕外层移回来。 三、(当内容满一屏时,)main层向外挤,防止footer叠加在main上。 此方法由于要计算footer负位移,不可处理变长div的footer。

如果用w3c链接的方法,使用flex布局可省略wrapper层。 直接给detail赋予display:flex 调整flex流向即可 ,并赋予main部分flex:1。flex:1等价于flex-grow:1。当所有元素排布时,该元素自动放大。由于flex布局时,float、clear属性失效,故无需再调整float相关属性。flex布局简单,且可处理高度变化的footer,vue组件中用flex有预处理。

本文在detail-wrapper中清除了浮动(用clearfix),其实在本例中也可以不清除,只是为了标准写法兼容其他场景。注意我们将detail-wrapper设置成了inline-block,为了避免垂直外边距合并问题。

<div v-show="detailShow" class="detail">
      <div class="detail-wrapper clearfix">
        <div class="detail-main">
        </div>
      </div>
      <div class="detail-close">
        <i class="icon-close"></i>
      </div>
    </div>
................................................................
    .detail
      position: fixed
      z-index: 100
      top: 0
      left: 0
      width: 100%
      height: 100%
      overflow: auto
      background-color: rgba(7, 17, 27, 0.8)
      .detail-wrapper
        width: 100%
        min-height: 100%
        .detail-main
          margin-top: 64px
          padding-bottom: 64px
      .detail-close
        position: relative
        width: 32px
        height: 32px
        margin: -64px auto 0 auto
        clear: both
        font-size: 32px

3.3. star组件抽象

实现detail-main部分。创建star组件。利用v-for指令。星级评价的星星切开单独用,不用雪碧图。有些class是通用css属性,有些是为了指定单独属性。stylus部分我们在这里定义不同的。

//star.vue

<template>
  <div class="star" :class="starType">
    <span v-for="itemClass in itemClasses" :class="itemClass" class="star-item" track-by="$index">
    </span>
  </div>

</template>

<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>

<style lang="stylus" rel="stylesheet/stylus">
  @import "../../common/stylus/mixin";

  .star
    font-size: 0
    .star-item
      display: inline-block
      background-repeat: no-repeat
    &.star-24
      .star-item
        width: 20px
        height: 20px
        margin-right: 22px
        background-size: 20px 20px
        &:last-child
          margin-right: 0
        &.on
          bg-image('star48_on')
        &.half
          bg-image('star48_half')
        &.off
          bg-image('star48_off')
    &.star-36
      .star-item
        width: 20px
        height: 20px
        margin-right: 22px
        background-size: 20px 20px
        &:last-child
          margin-right: 0
        &.on
          bg-image('star48_on')
        &.half
          bg-image('star48_half')
        &.off
          bg-image('star48_off')
    &.star-48
      .star-item
        width: 20px
        height: 20px
        margin-right: 22px
        background-size: 20px 20px
        &:last-child
          margin-right: 0
        &.on
          bg-image('star48_on')
        &.half
          bg-image('star48_half')
        &.off
          bg-image('star48_off')
</style>
//header.vue

      <div class="detail-main">
          <h1 class="name">{{seller.name}}</h1>
          <div class="star-wrapper">
            <star :size="48" :score=seller.score></star>
          </div>
        </div>
...
 components: {
      star
    }
...

3.4. 小标题自适应flex布局

这里又有面试题了:“优惠信息”居中,两条线自适应,背景还是透明的,不能做成一整条长线。 解决方法是flex布局,参考阮一峰教程。

          <div class="title">
            <div class="line"></div>
            <div class="text">优惠信息</div>
            <div class="line"></div>
          </div>
...
          .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-size: 14px
              font-weight: 700

3.5. header剩余部分实现

知识点没有什么新的,完善了一下剩余部分。给弹出层加了一个css3动画。

vue1.0中这样写:

 <div v-show="detailShow" class="detail" transition="fade">
...
...
 transition: all 0.5s
      &.fade-transition
        opacity: 1
        background-color: rgba(7, 17, 27, 0.8)
      &.fade-enter, &.fade-leave
        opacity: 0
        background: rgba(7, 17, 27, 0)

注意弹出层下层有毛玻璃效果,这里就不能用filter属性了,要用backdrop-filter。但是backdrop支持有限基本只有ios safari可以看到,可以看这篇https://hran.me/achieves/css3-backdrop-filter.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,423评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,339评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,241评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,503评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,824评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,262评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,615评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,337评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,989评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,300评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,829评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,193评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,753评论 3 230
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,970评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,708评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,295评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,207评论 2 258

推荐阅读更多精彩内容

  • 当空一天,你是否如我一样思念着远方,岁月是一杯浊酒,我们是酒中的沉淀物,经过时间的冲刷,虽然不如上好的美酒,但细细...
    蔡籽阅读 138评论 0 0
  • 本人自身是一个运营方面的初入者,最近上了一些有关于运营的课,有了自己的心得体会,故来粗浅的抒发一下。说的不对之处希...
    大黑兔丶阅读 346评论 2 1