美文网首页
Vue.js仿eleme项目(3)

Vue.js仿eleme项目(3)

作者: eastbaby | 来源:发表于2017-09-24 11:22 被阅读85次

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

    相关文章

      网友评论

          本文标题:Vue.js仿eleme项目(3)

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