zoukankan      html  css  js  c++  java
  • 【vue】/vue-ele-project

    作者大大的地址是:https://github.com/JinwenXie/vue-ele-project
    还是老办法,先运行项目看看效果

    我不算是外卖爱好者,不过觉得那个添加商品到购物车的动画效果很好看很有趣
    最近的框架好多,简直应接不暇,比如上篇的博客,我甚至以为作者大大自己封装一系列组件写项目,
    搜索了一下才发现居然是van有赞小程序。
    这个demo用到的是cube-ui也是一个框架,好像是滴滴的。
    我一下子就惊吓到了。新的技术层出不穷,我会的和用到的都比较少,做观众欣赏别人的代码和产品也很有意思。
    我们还是看代码吧

    //main.js
    import Vue from 'vue'
    import App from './App.vue'
    // 引入cube-ui
    import './cube-ui'
    // 引入注册组件文件
    import './register'
    
    import 'common/stylus/index.styl'
    
    Vue.config.productionTip = false
    
    new Vue({
      render: h => h(App)
    }).$mount('#app')
    

    主要说的意思就是页面是从app.vue进行渲染的,先看看register中引入了组件

    //register.js
    // cube-ui的用法
    import { createAPI } from 'cube-ui'
    import Vue from 'vue'
    import HeaderDetail from 'components/header-detail/header-detail'
    import ShopCartList from 'components/shop-cart-list/shop-cart-list'
    import ShopCartStikcy from 'components/shop-cart-sticky/shop-cart-sticky'
    import Food from 'components/food/food'
    
    createAPI(Vue, HeaderDetail)
    createAPI(Vue, ShopCartList)
    createAPI(Vue, ShopCartStikcy)
    createAPI(Vue, Food)
    

    接下来看app.vue中做了什么
    先看最上面的

    引入了组件

    //v-header.vue
    <template>
      <div class="header" @click="showDetail">
        <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">
              <support-ico :size=1 :type="seller.supports[0].type"></support-ico>
              <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">
          <img :src="seller.avatar" width="100%" height="100%">
        </div>
      </div>
    </template>
    
    <script type="text/ecmascript-6">
    // text/ecmascript-6 用来让编辑器识别vue文件中的es6语法
      import SupportIco from 'components/support-ico/support-ico'
    
      export default {
        name: 'v-header',
        props: {
          seller: {
            type: Object,
            default() {
              return {}
            }
          }
        },
        methods: {
          showDetail() {
            this.headerDetailComp = this.headerDetailComp || this.$createHeaderDetail({
              // 这个里面的是什么意思?
              $props: {
                seller: 'seller'
              }
            })
            this.headerDetailComp.show()
          }
        },
        components: {
          SupportIco
        }
      }
    </script>
    
    <style lang="stylus" rel="stylesheet/stylus">
      @import "~common/stylus/mixin"
      @import "~common/stylus/variable"
    
      .header
        position: relative
        overflow: hidden
        color: $color-white
        background: $color-background-ss
        .content-wrapper
          position: relative
          display: flex
          align-items: center
          padding: 24px 12px 18px 24px
          .avatar
            flex: 0 0 64px
             64px
            margin-right: 16px
            img
              border-radius: 2px
          .content
            flex: 1
            .title
              display: flex
              align-items: center
              margin-bottom: 8px
              .brand
                 30px
                height: 18px
                bg-image('brand')
                background-size: 30px 18px
                background-repeat: no-repeat
              .name
                margin-left: 6px
                font-size: $fontsize-large
                font-weight: bold
            .description
              margin-bottom: 8px
              line-height: 12px
              font-size: $fontsize-small
            .support
              display: flex
              align-items: center
              .support-ico
                margin-right: 4px
              .text
                line-height: 12px
                font-size: $fontsize-small-s
    
          .support-count
            position: absolute
            right: 12px
            bottom: 14px
            display: flex
            align-items: center
            padding: 0 8px
            height: 24px
            line-height: 24px
            text-align: center
            border-radius: 14px
            background: $color-background-sss
            .count
              font-size: $fontsize-small-s
            .icon-keyboard_arrow_right
              margin-left: 2px
              line-height: 24px
              font-size: $fontsize-small-s
    
        .bulletin-wrapper
          position: relative
          display: flex
          align-items: center
          height: 28px
          line-height: 28px
          padding: 0 8px
          background: $color-background-sss
          .bulletin-title
            flex: 0 0 22px
             22px
            height: 12px
            margin-right: 4px
            bg-image('bulletin')
            background-size: 22px 12px
            background-repeat: no-repeat
          .bulletin-text
            flex: 1
            white-space: nowrap
            overflow: hidden
            text-overflow: ellipsis
            font-size: $fontsize-small-s
          .icon-keyboard_arrow_right
            flex: 0 0 10px
             10px
            font-size: $fontsize-small-s
        .background
          position: absolute
          top: 0
          left: 0
           100%
          height: 100%
          z-index: -1
          filter: blur(10px)
    </style>
    

    看看v-header里面的SupportIco

    //不能理解里面写的是什么意思
    <template>
      <span class="support-ico" :class="iconCls"></span>
    </template>
    
    <script>
      export default {
        name: 'support-ico',
        props: {
          size: {
            type: Number
          },
          type: {
            type: Number
          }
        },
        computed: {
          iconCls() {
            const classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']
            return `icon-${this.size} ${classMap[this.type]}`
          }
        }
      }
    </script>
    
    <style lang="stylus" scoped>
      @import "~common/stylus/mixin"
    
      .support-ico
        display: inline-block
        background-repeat: no-repeat
    
      .icon-1
         12px
        height: 12px
        background-size: 12px 12px
        &.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')
    
      .icon-2
         16px
        height: 16px
        background-size: 16px 16px
        &.decrease
          bg-image('decrease_2')
        &.discount
          bg-image('discount_2')
        &.guarantee
          bg-image('guarantee_2')
        &.invoice
          bg-image('invoice_2')
        &.special
          bg-image('special_2')
    
      .icon-3
         12px
        height: 12px
        background-size: 12px 12px
        &.decrease
          bg-image('decrease_3')
        &.discount
          bg-image('discount_3')
        &.guarantee
          bg-image('guarantee_3')
        &.invoice
          bg-image('invoice_3')
        &.special
          bg-image('special_3')
    
      .icon-4
         16px
        height: 16px
        background-size: 16px 16px
        &.decrease
          bg-image('decrease_4')
        &.discount
          bg-image('discount_4')
        &.guarantee
          bg-image('guarantee_4')
        &.invoice
          bg-image('invoice_4')
        &.special
          bg-image('special_4')
    </style>
    

    vue提供的 @touchmove.prevent 可以用来阻止滑动,但是这个方法会对其内的子div的滑动事件也禁止掉了,这样会导致中间文字无法滑动。
    如果没有中间滑动需求,用 @touchmove.prevent 实现是一个很好的方法。
    我们接下来看seller组件
    他的页面是

    呃呃,其实想不明白,为啥要在header中传入sell组件

    //seller组件
    <template>
      <cube-scroll class="seller" :options="sellerScrollOptions">
        <div class="seller-content">
          <div class="overview">
            <h1 class="title">{{seller.name}}</h1>
            <div class="desc border-bottom-1px">
              <star :size="36" :score="seller.score"></star>
              <span class="text">({{seller.ratingCount}})</span>
              <span class="text">月售{{seller.sellCount}}单</span>
            </div>
            <ul class="remark">
              <li class="block">
                <h2>起送价</h2>
                <div class="content">
                  <span class="stress">{{seller.minPrice}}</span>元
                </div>
              </li>
              <li class="block">
                <h2>商家配送</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryPrice}}</span>元
                </div>
              </li>
              <li class="block">
                <h2>平均配送时间</h2>
                <div class="content">
                  <span class="stress">{{seller.deliveryTime}}</span>分钟
                </div>
              </li>
            </ul>
            <div class="favorite" @click="toggleFavorite">
              <span class="icon-favorite" :class="{'active':favorite}"></span>
              <span class="text">{{favoriteText}}</span>
            </div>
          </div>
          <split></split>
          <div class="bulletin">
            <h1 class="title">公告与活动</h1>
            <div class="content-wrapper border-bottom-1px">
              <p class="content">{{seller.bulletin}}</p>
            </div>
            <ul v-if="seller.supports" class="supports">
              <li
                class="support-item border-bottom-1px"
                v-for="(item,index) in seller.supports"
                :key="index"
              >
                <support-ico :size=4 :type="seller.supports[index].type"></support-ico>
                <span class="text">{{seller.supports[index].description}}</span>
              </li>
            </ul>
          </div>
          <split></split>
          <div class="pics">
            <h1 class="title">商家实景</h1>
            <cube-scroll class="pic-wrapper" :options="picScrollOptions">
              <ul class="pic-list">
                <li class="pic-item"
                    v-for="(pic,index) in seller.pics"
                    :key="index"
                >
                  <img :src="pic" width="120" height="90">
                </li>
              </ul>
            </cube-scroll>
          </div>
          <split></split>
          <div class="info">
            <h1 class="title border-bottom-1px">商家信息</h1>
            <ul>
              <li
                class="info-item border-bottom-1px"
                v-for="(info,index) in seller.infos"
                :key="index"
              >
                {{info}}
              </li>
            </ul>
          </div>
        </div>
      </cube-scroll>
    </template>
    
    <script>
      import { saveToLocal, loadFromLocal } from 'common/js/storage'
      import Star from 'components/star/star'
      import Split from 'components/split/split'
      import SupportIco from 'components/support-ico/support-ico'
    
      export default {
        props: {
          data: {
            type: Object,
            default() {
              return {}
            }
          }
        },
        data() {
          return {
            favorite: false,
            sellerScrollOptions: {
              directionLockThreshold: 0,
              click: false
            },
            picScrollOptions: {
              scrollX: true,
              scrollY: false,
              stopPropagation: true,
              directionLockThreshold: 0
            }
          }
        },
        computed: {
          seller() {
            return this.data.seller || {}
          },
          favoriteText() {
            return this.favorite ? '已收藏' : '收藏'
          }
        },
        created() {
          this.favorite = loadFromLocal(this.seller.id, 'favorite', false)
        },
        methods: {
          toggleFavorite() {
            this.favorite = !this.favorite
            saveToLocal(this.seller.id, 'favorite', this.favorite)
          }
        },
        components: {
          SupportIco,
          Star,
          Split
        }
      }
    </script>
    
    <style lang="stylus" scoped>
      @import "~common/stylus/variable"
      @import "~common/stylus/mixin"
    
      .seller
        height: 100%
        text-align: left
        .overview
          position: relative
          padding: 18px
          .title
            margin-bottom: 8px
            line-height: 14px
            font-size: $fontsize-medium
            color: $color-dark-grey
          .desc
            display: flex
            align-items: center
            padding-bottom: 18px
            .star
              margin-right: 8px
            .text
              margin-right: 12px
              line-height: 18px
              font-size: $fontsize-small-s
              color: $color-grey
          .remark
            display: flex
            padding-top: 18px
            .block
              flex: 1
              text-align: center
              border-right: 1px solid $color-col-line
              &:last-child
                border: none
              h2
                margin-bottom: 4px
                line-height: 10px
                font-size: $fontsize-small-s
                color: $color-light-grey
              .content
                line-height: 24px
                font-size: $fontsize-small-s
                color: $color-dark-grey
                .stress
                  font-size: $fontsize-large-xxx
          .favorite
            position: absolute
             50px
            right: 11px
            top: 18px
            text-align: center
            .icon-favorite
              display: block
              margin-bottom: 4px
              line-height: 24px
              font-size: $fontsize-large-xxx
              color: $color-light-grey-s
              &.active
                color: $color-red
            .text
              line-height: 10px
              font-size: $fontsize-small-s
              color: $color-grey
        .bulletin
          padding: 18px 18px 0 18px
          white-space: normal
          .title
            margin-bottom: 8px
            line-height: 14px
            color: $color-dark-grey
            font-size: $fontsize-medium
          .content-wrapper
            padding: 0 12px 16px 12px
            .content
              line-height: 24px
              font-size: $fontsize-small
              color: $color-red
          .supports
            .support-item
              display: flex
              align-items: center
              padding: 16px 12px
              &:last-child
                border-none()
            .support-ico
              margin-right: 6px
            .text
              line-height: 16px
              font-size: $fontsize-small
              color: $color-dark-grey
        .pics
          padding: 18px
          .title
            margin-bottom: 12px
            line-height: 14px
            color: $color-dark-grey
            font-size: $fontsize-medium
          .pic-wrapper
            display: flex
            align-items: center
            .pic-list
              .pic-item
                display: inline-block
                margin-right: 6px
                 120px
                height: 90px
                &:last-child
                  margin: 0
        .info
          padding: 18px 18px 0 18px
          color: $color-dark-grey
          .title
            padding-bottom: 12px
            line-height: 14px
            font-size: $fontsize-medium
          .info-item
            padding: 16px 12px
            line-height: 16px
            font-size: $fontsize-small
            &:last-child
              border-none()
    </style>
    

    看一下common/js/storage

    //srccommonjsstorage.js
    import storage from 'good-storage'
    // vue搜索历史本地存储-good-storage
    
    const SELLER_KEY = '__seller__'
    
    export function saveToLocal(id, key, val) {
      const seller = storage.get(SELLER_KEY, {})
      if (!seller[id]) {
        seller[id] = {}
      }
      seller[id][key] = val
      storage.set(SELLER_KEY, seller)
    }
    
    export function loadFromLocal(id, key, def) {
      const seller = storage.get(SELLER_KEY, {})
      if (!seller[id]) {
        return def
      }
      return seller[id][key] || def
    }
    
    //srccomponentsstarstar.vue
    <template>
      <div class="star" :class="starType">
        <span v-for="(itemClass,index) in itemClasses" :class="itemClass" class="star-item" :key="index"></span>
      </div>
    </template>
    
    <script>
      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 = []
            const score = Math.floor(this.score * 2) / 2
            const hasDecimal = score % 1 !== 0
            const 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.styl"
    
      .star
        display: flex
        align-items: center
        justify-content: center
        .star-item
          background-repeat: no-repeat
        &.star-48
          .star-item
             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
             15px
            height: 15px
            margin-right: 6px
            background-size: 15px 15px
            &:last-child
              margin-right: 0
            &.on
              bg-image('star36_on')
            &.half
              bg-image('star36_half')
            &.off
              bg-image('star36_off')
        &.star-24
          .star-item
             10px
            height: 10px
            margin-right: 3px
            background-size: 10px 10px
            &:last-child
              margin-right: 0
            &.on
              bg-image('star24_on')
            &.half
              bg-image('star24_half')
            &.off
              bg-image('star24_off')
    </style>
    

    上面内容没有看懂

    //split.vue
    <template>
      <div class="split"></div>
    </template>
    
    <script>
      export default {
        name: 'split'
      }
    </script>
    
    <style lang="stylus" scoped>
      @import "~common/stylus/variable"
      .split
         100%
        height: 16px
        border-top: 1px solid $color-row-line
        border-bottom: 1px solid $color-row-line
        background: $color-background-ssss
    </style>
    
    //srccomponentssupport-icosupport-ico.vue
    <template>
      <span class="support-ico" :class="iconCls"></span>
    </template>
    
    <script>
      export default {
        name: 'support-ico',
        props: {
          size: {
            type: Number
          },
          type: {
            type: Number
          }
        },
        computed: {
          iconCls() {
            const classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']
            return `icon-${this.size} ${classMap[this.type]}`
          }
        }
      }
    </script>
    
    <style lang="stylus" scoped>
      @import "~common/stylus/mixin"
    
      .support-ico
        display: inline-block
        background-repeat: no-repeat
    
      .icon-1
         12px
        height: 12px
        background-size: 12px 12px
        &.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')
    
      .icon-2
         16px
        height: 16px
        background-size: 16px 16px
        &.decrease
          bg-image('decrease_2')
        &.discount
          bg-image('discount_2')
        &.guarantee
          bg-image('guarantee_2')
        &.invoice
          bg-image('invoice_2')
        &.special
          bg-image('special_2')
    
      .icon-3
         12px
        height: 12px
        background-size: 12px 12px
        &.decrease
          bg-image('decrease_3')
        &.discount
          bg-image('discount_3')
        &.guarantee
          bg-image('guarantee_3')
        &.invoice
          bg-image('invoice_3')
        &.special
          bg-image('special_3')
    
      .icon-4
         16px
        height: 16px
        background-size: 16px 16px
        &.decrease
          bg-image('decrease_4')
        &.discount
          bg-image('discount_4')
        &.guarantee
          bg-image('guarantee_4')
        &.invoice
          bg-image('invoice_4')
        &.special
          bg-image('special_4')
    </style>
    

    上面内容我没有看懂
    接下来看tab里面的内容

    在App.vue中引入了goods组件

    //goods.vue
    <template>
      <div class="goods">
        <div class="scroll-nav-wrapper">
          <cube-scroll-nav
            :side=true
            :data="goods"
            :options="scrollOptions"
            v-if="goods.length"
          >
            <template slot="bar" slot-scope="props">
              <cube-scroll-nav-bar
                direction="vertical"
                :labels="props.labels"
                :txts="barTxts"
                :current="props.current"
              >
                <template slot-scope="props">
                  <div class="text">
                    <support-ico
                      v-if="props.txt.type>=1"
                      :size=3
                      :type="props.txt.type"
                    ></support-ico>
                    <span>{{props.txt.name}}</span>
                    <span class="num" v-if="props.txt.count">
                      <bubble :num="props.txt.count"></bubble>
                    </span>
                  </div>
                </template>
              </cube-scroll-nav-bar>
            </template>
            <cube-scroll-nav-panel
              v-for="good in goods"
              :key="good.name"
              :label="good.name"
              :title="good.name"
            >
              <ul>
                <li
                  @click="selectFood(food)"
                  v-for="food in good.foods"
                  :key="food.name"
                  class="food-item"
                >
                  <div class="icon">
                    <img width="57" height="57" :src="food.icon">
                  </div>
                  <div class="content">
                    <h2 class="name">{{food.name}}</h2>
                    <p class="desc">{{food.description}}</p>
                    <div class="extra">
                      <span class="count">月售{{food.sellCount}}份</span><span>好评率{{food.rating}}%</span>
                    </div>
                    <div class="price">
                      <span class="now">¥{{food.price}}</span>
                      <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                    </div>
                    <div class="cart-control-wrapper">
                      <cart-control @add="onAdd" :food="food"></cart-control>
                    </div>
                  </div>
                </li>
              </ul>
            </cube-scroll-nav-panel>
          </cube-scroll-nav>
        </div>
        <div class="shop-cart-wrapper">
          <shop-cart
            ref="shopCart"
            :select-foods="selectFoods"
            :delivery-price="seller.deliveryPrice"
            :min-price="seller.minPrice"></shop-cart>
        </div>
        <!--<food :food="selectedFood" ref="food"></food>-->
      </div>
    </template>
    
    <script>
      import { getGoods } from 'api'
      import CartControl from 'components/cart-control/cart-control'
      import ShopCart from 'components/shop-cart/shop-cart'
      import SupportIco from 'components/support-ico/support-ico'
      import Bubble from 'components/bubble/bubble'
    
      export default {
        name: 'goods',
        props: {
          data: {
            type: Object,
            default() {
              return {}
            }
          }
        },
        data() {
          return {
            goods: [],
            selectedFood: {},
            scrollOptions: {
              click: false,
              directionLockThreshold: 0
            }
          }
        },
        computed: {
          seller() {
            return this.data.seller
          },
          selectFoods() {
            let foods = []
            this.goods.forEach((good) => {
              console.log('good....', good)
              // 将数据渲染在页面中
              good.foods.forEach((food) => {
                if (food.count) {
                  foods.push(food)
                }
              })
            })
            return foods
          },
          barTxts() {
            let ret = []
            this.goods.forEach((good) => {
              console.log('......good', good)
              const { type, name, foods } = good
              let count = 0
              foods.forEach((food) => {
                count += food.count || 0
              })
              ret.push({
                type,
                name,
                count
              })
            })
            return ret
          }
        },
        methods: {
          fetch() {
            if (!this.fetched) {
              this.fetched = true
              getGoods({
                id: this.seller.id
              }).then((goods) => {
                console.log('.......app', goods)
                this.goods = goods
              })
            }
          },
          selectFood(food) {
            this.selectedFood = food
            this._showFood()
            this._showShopCartSticky()
          },
          onAdd(target) {
            this.$refs.shopCart.drop(target)
          },
          _showFood() {
            this.foodComp = this.foodComp || this.$createFood({
              $props: {
                food: 'selectedFood'
              },
              $events: {
                add: (target) => {
                  this.shopCartStickyComp.drop(target)
                },
                leave: () => {
                  this._hideShopCartSticky()
                }
              }
            })
            this.foodComp.show()
          },
          _showShopCartSticky() {
            this.shopCartStickyComp = this.shopCartStickyComp || this.$createShopCartSticky({
              $props: {
                selectFoods: 'selectFoods',
                deliveryPrice: this.seller.deliveryPrice,
                minPrice: this.seller.minPrice,
                fold: true
              }
            })
            this.shopCartStickyComp.show()
          },
          _hideShopCartSticky() {
            this.shopCartStickyComp.hide()
          }
        },
        components: {
          Bubble,
          SupportIco,
          CartControl,
          ShopCart
        }
      }
    </script>
    
    <style lang="stylus" scoped>
      @import "~common/stylus/mixin"
      @import "~common/stylus/variable"
      .goods
        position: relative
        text-align: left
        height: 100%
        .scroll-nav-wrapper
          position: absolute
           100%
          top: 0
          left: 0
          bottom: 48px
        >>> .cube-scroll-nav-bar
           80px
          white-space: normal
          overflow: hidden
        >>> .cube-scroll-nav-bar-item
          padding: 0 10px
          display: flex
          align-items: center
          height: 56px
          line-height: 14px
          font-size: $fontsize-small
          background: $color-background-ssss
          .text
            flex: 1
            position: relative
          .num
            position: absolute
            right: -8px
            top: -10px
          .support-ico
            display: inline-block
            vertical-align: top
            margin-right: 4px
        >>> .cube-scroll-nav-bar-item_active
          background: $color-white
          color: $color-dark-grey
        >>> .cube-scroll-nav-panel-title
          padding-left: 14px
          height: 26px
          line-height: 26px
          border-left: 2px solid $color-col-line
          font-size: $fontsize-small
          color: $color-grey
          background: $color-background-ssss
        .food-item
          display: flex
          margin: 18px
          padding-bottom: 18px
          position: relative
          &:last-child
            border-none()
            margin-bottom: 0
          .icon
            flex: 0 0 57px
            margin-right: 10px
            img
              height: auto
          .content
            flex: 1
            .name
              margin: 2px 0 8px 0
              height: 14px
              line-height: 14px
              font-size: $fontsize-medium
              color: $color-dark-grey
            .desc, .extra
              line-height: 10px
              font-size: $fontsize-small-s
              color: $color-light-grey
            .desc
              line-height: 12px
              margin-bottom: 8px
            .extra
              .count
                margin-right: 12px
            .price
              font-weight: 700
              line-height: 24px
              .now
                margin-right: 8px
                font-size: $fontsize-medium
                color: $color-red
              .old
                text-decoration: line-through
                font-size: $fontsize-small-s
                color: $color-light-grey
          .cart-control-wrapper
            position: absolute
            right: 0
            bottom: 12px
        .shop-cart-wrapper
          position: absolute
          left: 0
          bottom: 0
          z-index: 50
           100%
          height: 48px
    </style>
    
    
    //srccomponentscart-controlcart-control.vue
    <template>
      <div class="cartcontrol">
        <transition name="move">
          <div class="cart-decrease" v-show="food.count>0" @click.stop="decrease">
            <span class="inner icon-remove_circle_outline"></span>
          </div>
        </transition>
        <div class="cart-count" v-show="food.count>0">{{food.count}}</div>
        <div class="cart-add icon-add_circle" @click.stop="add"></div>
      </div>
    </template>
    
    <script>
      const EVENT_ADD = 'add'
    
      export default {
        name: 'cart-control',
        props: {
          food: {
            type: Object
          }
        },
        methods: {
          add(event) {
            if (!this.food.count) {
              this.$set(this.food, 'count', 1)
            } else {
              this.food.count++
            }
            this.$emit(EVENT_ADD, event.target)
          },
          decrease() {
            if (this.food.count) {
              this.food.count--
            }
          }
        }
      }
    </script>
    
    <style lang="stylus" scoped>
      @import "~common/stylus/variable"
    
      .cartcontrol
        display: flex
        align-items: center
        .cart-decrease
          display: inline-block
          padding: 6px
          opacity: 1
          .inner
            display: inline-block
            line-height: 24px
            font-size: $fontsize-large-xxx
            color: $color-blue
            transition: all 0.4s linear
            transform: rotate(0)
          &.move-enter-active, &.move-leave-active
            transition: all 0.4s linear
          &.move-enter, &.move-leave-active
            opacity: 0
            transform: translate3d(24px, 0, 0)
            .inner
              transform: rotate(180deg)
        .cart-count
           12px
          line-height: 24px
          text-align: center
          font-size: $fontsize-small-s
          color: $color-grey
        .cart-add
          display: inline-block
          padding: 6px
          line-height: 24px
          font-size: $fontsize-large-xxx
          color: $color-blue
    </style>
    
    //srccomponentsshop-cartshop-cart.vue
    <template>
      <div>
        <div class="shopcart">
          <div class="content" @click="toggleList">
            <div class="content-left">
              <div class="logo-wrapper">
                <div class="logo" :class="{'highlight':totalCount>0}">
                  <i class="icon-shopping_cart" :class="{'highlight':totalCount>0}"></i>
                </div>
                <div class="num" v-show="totalCount>0">
                  <bubble :num="totalCount"></bubble>
                </div>
              </div>
              <div class="price" :class="{'highlight':totalPrice>0}">¥{{totalPrice}}</div>
              <div class="desc">另需配送费¥{{deliveryPrice}}元</div>
            </div>
            <div class="content-right" @click="pay">
              <div class="pay" :class="payClass">
                {{payDesc}}
              </div>
            </div>
          </div>
          <!-- 关于抛物线的那个 -->
          <div class="ball-container">
            <div v-for="(ball,index) in balls" :key="index">
              <transition
                @before-enter="beforeDrop"
                @enter="dropping"
                @after-enter="afterDrop">
                <div class="ball" v-show="ball.show">
                  <div class="inner inner-hook"></div>
                </div>
              </transition>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
      import Bubble from 'components/bubble/bubble'
    
      const BALL_LEN = 10
      const innerClsHook = 'inner-hook'
    
      function createBalls() {
        let balls = []
        for (let i = 0; i < BALL_LEN; i++) {
          balls.push({ show: false })
        }
        return balls
      }
    
      export default {
        name: 'shop-cart',
        props: {
          selectFoods: {
            type: Array,
            default() {
              return []
            }
          },
          deliveryPrice: {
            type: Number,
            default: 0
          },
          minPrice: {
            type: Number,
            default: 0
          },
          sticky: {
            type: Boolean,
            default: false
          },
          fold: {
            type: Boolean,
            default: true
          }
        },
        data() {
          return {
            balls: createBalls(),
            listFold: this.fold
          }
        },
        created() {
          this.dropBalls = []
        },
        computed: {
          totalPrice() {
            let total = 0
            this.selectFoods.forEach((food) => {
              total += food.price * food.count
            })
            return total
          },
          totalCount() {
            let count = 0
            this.selectFoods.forEach((food) => {
              count += food.count
            })
            return count
          },
          payDesc() {
            if (this.totalPrice === 0) {
              return `¥${this.minPrice}元起送`
            } else if (this.totalPrice < this.minPrice) {
              let diff = this.minPrice - this.totalPrice
              return `还差¥${diff}元起送`
            } else {
              return '去结算'
            }
          },
          payClass() {
            if (!this.totalCount || this.totalPrice < this.minPrice) {
              return 'not-enough'
            } else {
              return 'enough'
            }
          }
        },
        methods: {
          toggleList() {
            if (this.listFold) {
              if (!this.totalCount) {
                return
              }
              this.listFold = false
              this._showShopCartList()
              this._showShopCartSticky()
            } else {
              this.listFold = true
              this._hideShopCartList()
            }
          },
          pay(e) {
            if (this.totalPrice < this.minPrice) {
              return
            }
            this.$createDialog({
              title: '支付',
              content: `您需要支付${this.totalPrice}元`
            }).show()
            e.stopPropagation()
          },
          drop(el) {
            for (let i = 0; i < this.balls.length; i++) {
              const ball = this.balls[i]
              if (!ball.show) {
                ball.show = true
                ball.el = el
                this.dropBalls.push(ball)
                return
              }
            }
          },
          // 丢之前
          beforeDrop(el) {
            const ball = this.dropBalls[this.dropBalls.length - 1]
            const rect = ball.el.getBoundingClientRect()
            // getBoundingClientRect用于获取元素相对与浏览器视口的位置
            const x = rect.left - 32
            const y = -(window.innerHeight - rect.top - 22)
            el.style.display = ''
            el.style.transform = el.style.webkitTransform = `translate3d(0,${y}px,0)`
            const inner = el.getElementsByClassName(innerClsHook)[0]
            inner.style.transform = inner.style.webkitTransform = `translate3d(${x}px,0,0)`
          },
          dropping(el, done) {
            this._reflow = document.body.offsetHeight
            el.style.transform = el.style.webkitTransform = `translate3d(0,0,0)`
            const inner = el.getElementsByClassName(innerClsHook)[0]
            inner.style.transform = inner.style.webkitTransform = `translate3d(0,0,0)`
            el.addEventListener('transitionend', done)
          },
          afterDrop(el) {
            const ball = this.dropBalls.shift()
            if (ball) {
              ball.show = false
              el.style.display = 'none'
            }
          },
          _showShopCartList() {
            this.shopCartListComp = this.shopCartListComp || this.$createShopCartList({
              $props: {
                selectFoods: 'selectFoods'
              },
              $events: {
                leave: () => {
                  this._hideShopCartSticky()
                },
                hide: () => {
                  this.listFold = true
                },
                add: (el) => {
                  this.shopCartStickyComp.drop(el)
                }
              }
            })
            this.shopCartListComp.show()
          },
          _showShopCartSticky() {
            this.shopCartStickyComp = this.shopCartStickyComp || this.$createShopCartSticky({
              $props: {
                selectFoods: 'selectFoods',
                deliveryPrice: 'deliveryPrice',
                minPrice: 'minPrice',
                fold: 'listFold',
                list: this.shopCartListComp
              }
            })
            this.shopCartStickyComp.show()
          },
          _hideShopCartList() {
            const list = this.sticky ? this.$parent.list : this.shopCartListComp
            list.hide && list.hide()
          },
          _hideShopCartSticky() {
            this.shopCartStickyComp.hide()
          }
        },
        watch: {
          fold(newVal) {
            this.listFold = newVal
          },
          totalCount(count) {
            if (!this.fold && count === 0) {
              this._hideShopCartList()
            }
          }
        },
        components: {
          Bubble
        }
      }
    </script>
    
    <style lang="stylus" scoped>
      @import "~common/stylus/mixin"
      @import "~common/stylus/variable"
    
      .shopcart
        height: 100%
        .content
          display: flex
          background: $color-background
          font-size: 0
          color: $color-light-grey
          .content-left
            flex: 1
            .logo-wrapper
              display: inline-block
              vertical-align: top
              position: relative
              top: -10px
              margin: 0 12px
              padding: 6px
               56px
              height: 56px
              box-sizing: border-box
              border-radius: 50%
              background: $color-background
              .logo
                 100%
                height: 100%
                border-radius: 50%
                text-align: center
                background: $color-dark-grey
                &.highlight
                  background: $color-blue
                .icon-shopping_cart
                  line-height: 44px
                  font-size: $fontsize-large-xxx
                  color: $color-light-grey
                  &.highlight
                    color: $color-white
              .num
                position: absolute
                top: 0
                right: 0
            .price
              display: inline-block
              vertical-align: top
              margin-top: 12px
              line-height: 24px
              padding-right: 12px
              box-sizing: border-box
              border-right: 1px solid rgba(255, 255, 255, 0.1)
              font-weight: 700
              font-size: $fontsize-large
              &.highlight
                color: $color-white
            .desc
              display: inline-block
              vertical-align: top
              margin: 12px 0 0 12px
              line-height: 24px
              font-size: $fontsize-small-s
          .content-right
            flex: 0 0 105px
             105px
            .pay
              height: 48px
              line-height: 48px
              text-align: center
              font-weight: 700
              font-size: $fontsize-small
              &.not-enough
                background: $color-dark-grey
              &.enough
                background: $color-green
                color: $color-white
        .ball-container
          .ball
            position: fixed
            left: 32px
            bottom: 22px
            z-index: 200
            transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41)
            .inner
               16px
              height: 16px
              border-radius: 50%
              background: $color-blue
              transition: all 0.4s linear
    </style>
    
    //srccomponentsubbleubble.vue
    <template>
      <span class="bubble">
        {{ num }}
      </span>
    </template>
    
    <script>
      export default {
        name: 'bubble',
        props: {
          num: {
            type: Number
          }
        }
      }
    </script>
    
    <style lang="stylus" scoped>
      @import "~common/stylus/variable"
    
      .bubble
        display: inline-block
        padding: 0 5px
        height: 16px
        line-height: 16px
        text-align: center
        border-radius: 16px
        font-family: Helvetica
        font-weight: 700
        font-size: $fontsize-small-s
        color: $color-white
        background: linear-gradient(to right, $color-orange, $color-red)
    </style>
    


    再看rating组件

    <template>
      <cube-scroll ref="scroll" class="ratings" :options="scrollOptions">
        <div class="ratings-content">
          <div class="overview">
            <div class="overview-left">
              <h1 class="score">{{seller.score}}</h1>
              <div class="title">综合评分</div>
              <div class="rank">高于周边商家{{seller.rankRate}}%</div>
            </div>
            <div class="overview-right">
              <div class="score-wrapper">
                <span class="title">服务态度</span>
                <star :size="36" :score="seller.serviceScore"></star>
                <span class="score">{{seller.serviceScore}}</span>
              </div>
              <div class="score-wrapper">
                <span class="title">商品评分</span>
                <star :size="36" :score="seller.foodScore"></star>
                <span class="score">{{seller.foodScore}}</span>
              </div>
              <div class="delivery-wrapper">
                <span class="title">送达时间</span>
                <span class="delivery">{{seller.deliveryTime}}分钟</span>
              </div>
            </div>
          </div>
          <split></split>
          <rating-select
            @select="onSelect"
            @toggle="onToggle"
            :selectType="selectType"
            :onlyContent="onlyContent"
            :ratings="ratings"
          >
          </rating-select>
          <div class="rating-wrapper">
            <ul>
              <li
                v-for="(rating,index) in computedRatings"
                :key="index"
                class="rating-item border-bottom-1px"
              >
                <div class="avatar">
                  <img width="28" height="28" :src="rating.avatar">
                </div>
                <div class="content">
                  <h1 class="name">{{rating.username}}</h1>
                  <div class="star-wrapper">
                    <star :size="24" :score="rating.score"></star>
                    <span class="delivery" v-show="rating.deliveryTime">{{rating.deliveryTime}}</span>
                  </div>
                  <p class="text">{{rating.text}}</p>
                  <div class="recommend" v-show="rating.recommend && rating.recommend.length">
                    <span class="icon-thumb_up"></span>
                    <span
                      class="item"
                      v-for="(item,index) in rating.recommend"
                      :key="index"
                    >
                      {{item}}
                    </span>
                  </div>
                  <div class="time">
                    {{format(rating.rateTime)}}
                  </div>
                </div>
              </li>
            </ul>
          </div>
        </div>
      </cube-scroll>
    </template>
    
    <script>
      import Star from 'components/star/star'
      import RatingSelect from 'components/rating-select/rating-select'
      import Split from 'components/split/split'
      import ratingMixin from 'common/mixins/rating'
      import { getRatings } from 'api'
      import moment from 'moment'
    
      export default {
        name: 'ratings',
        mixins: [ratingMixin],
        props: {
          data: {
            type: Object
          }
        },
        data () {
          return {
            ratings: [],
            scrollOptions: {
              click: false,
              directionLockThreshold: 0
            }
          }
        },
        computed: {
          seller () {
            return this.data.seller || {}
          }
        },
        methods: {
          fetch () {
            if (!this.fetched) {
              this.fetched = true
              getRatings({
                id: this.seller.id
              }).then((ratings) => {
                this.ratings = ratings
              })
            }
          },
          format (time) {
            return moment(time).format('YYYY-MM-DD hh:mm')
          }
        },
        components: {
          Star,
          Split,
          RatingSelect
        },
        watch: {
          selectType () {
            this.$nextTick(() => {
              this.$refs.scroll.refresh()
            })
          }
        }
      }
    </script>
    
    <style lang="stylus" scoped>
      @import "~common/stylus/variable"
      @import "~common/stylus/mixin"
    
      .ratings
        position: relative
        text-align: left
        white-space: normal
        height: 100%
        .overview
          display: flex
          padding: 18px 0
          .overview-left
            flex: 0 0 137px
            padding: 6px 0
             137px
            border-right: 1px solid $color-col-line
            text-align: center
            @media only screen and (max- 320px)
              flex: 0 0 120px
               120px
            .score
              margin-bottom: 6px
              line-height: 28px
              font-size: $fontsize-large-xxx
              color: $color-orange
            .title
              margin-bottom: 8px
              line-height: 12px
              font-size: $fontsize-small
              color: $color-dark-grey
            .rank
              line-height: 10px
              font-size: $fontsize-small-s
              color: $color-light-grey
          .overview-right
            flex: 1
            padding: 6px 0 6px 24px
            @media only screen and (max- 320px)
              padding-left: 6px
            .score-wrapper
              display: flex
              align-items: center
              margin-bottom: 8px
              .title
                line-height: 18px
                font-size: $fontsize-small
                color: $color-dark-grey
              .star
                margin: 0 12px
              .score
                line-height: 18px
                font-size: $fontsize-small
                color: $color-orange
            .delivery-wrapper
              display: flex
              align-items: center
              .title
                line-height: 18px
                font-size: $fontsize-small
                color: $color-dark-grey
              .delivery
                margin-left: 12px
                font-size: $fontsize-small
                color: $color-light-grey
        .rating-wrapper
          padding: 0 18px
          .rating-item
            display: flex
            padding: 18px 0
            &:last-child
              border-none()
            .avatar
              flex: 0 0 28px
               28px
              margin-right: 12px
              img
                height: auto
                border-radius: 50%
            .content
              position: relative
              flex: 1
              .name
                margin-bottom: 4px
                line-height: 12px
                font-size: $fontsize-small-s
                color: $color-dark-grey
              .star-wrapper
                margin-bottom: 6px
                display: flex
                align-items: center
                .star
                  margin-right: 6px
                .delivery
                  font-size: $fontsize-small-s
                  color: $color-light-grey
              .text
                margin-bottom: 8px
                line-height: 18px
                color: $color-dark-grey
                font-size: $fontsize-small
              .recommend
                display: flex
                align-items: center
                flex-wrap: wrap
                line-height: 16px
                .icon-thumb_up, .item
                  margin: 0 8px 4px 0
                  font-size: $fontsize-small-s
                .icon-thumb_up
                  color: $color-blue
                .item
                  padding: 0 6px
                  border: 1px solid $color-row-line
                  border-radius: 1px
                  color: $color-light-grey
                  background: $color-white
              .time
                position: absolute
                top: 0
                right: 0
                line-height: 12px
                font-size: $fontsize-small
                color: $color-light-grey
    </style>
    
    //srccomponents	ab	ab.vue
    <template>
      <div class="tab">
        <cube-tab-bar
          :useTransition=false
          :showSlider=true
          v-model="selectedLabel"
          :data="tabs"
          ref="tabBar"
          class="border-bottom-1px"
        >
        </cube-tab-bar>
        <div class="slide-wrapper">
          <cube-slide
            :loop=false
            :auto-play=false
            :show-dots=false
            :initial-index="index"
            ref="slide"
            :options="slideOptions"
            @scroll="onScroll"
            @change="onChange"
          >
            <cube-slide-item v-for="(tab,index) in tabs" :key="index">
              <component ref="component" :is="tab.component" :data="tab.data"></component>
            </cube-slide-item>
          </cube-slide>
        </div>
      </div>
    </template>
    
    <script>
      export default {
        name: 'tab',
        props: {
          tabs: {
            type: Array,
            default() {
              return []
            }
          },
          initialIndex: {
            type: Number,
            default: 0
          }
        },
        data() {
          return {
            index: this.initialIndex,
            slideOptions: {
              listenScroll: true,
              probeType: 3,
              directionLockThreshold: 0
            }
          }
        },
        computed: {
          selectedLabel: {
            get() {
              return this.tabs[this.index].label
            },
            set(newVal) {
              this.index = this.tabs.findIndex((value) => {
                return value.label === newVal
              })
            }
          }
        },
        mounted() {
          this.onChange(this.index)
        },
        methods: {
          onScroll(pos) {
            const tabBarWidth = this.$refs.tabBar.$el.clientWidth
            const slideWidth = this.$refs.slide.slide.scrollerWidth
            const transform = -pos.x / slideWidth * tabBarWidth
            this.$refs.tabBar.setSliderTransform(transform)
          },
          onChange(current) {
            this.index = current
            const instance = this.$refs.component[current]
            if (instance && instance.fetch) {
              instance.fetch()
            }
          }
        }
      }
    </script>
    
    <style lang="stylus" scoped>
      @import "~common/stylus/variable"
    
      .tab
        display: flex
        flex-direction: column
        height: 100%
        >>> .cube-tab
          padding: 10px 0
        .slide-wrapper
          flex: 1
          overflow: hidden
    </style>
    

    上面的代码:我并没有看懂,应该花一段时间专门学习,还有我发现有些基础知识点我不会。

  • 相关阅读:
    Android中LayoutInflater的使用
    m2014-architecture-webserver->百万记录级mysql数据库及Discuz!论坛优化
    m2014-architecture-imgserver->利用Squid反向代理搭建CDN缓存服务器加快Web访问速度
    m2014-architecture-imgserver->Lighttpd +mod_mem_cache的效果简直太好了
    m2014-architecture-imgserver->配置lighttpd mod_mem_cache 模块做静态资源服务器
    m2014-architecture-imgserver->Lighttpd Mod_Cache很简单很强大的动态缓存
    雅虎十四条
    java高新技术-基本数据类型拆装箱及享元设计模式
    java高新技术-可变参数与OverLoad相关面试题分析
    java高新技术-java5的静态导入与编译器语法设置
  • 原文地址:https://www.cnblogs.com/smart-girl/p/11138943.html
Copyright © 2011-2022 走看看