tabs/tabs.vue

<template>
    <article class="ion-tabs" :class="[modeClass,colorClass]"
             :tabsLayout="tabsLayout"
             :tabsHighlight="tabsHighlight"
             :tabsPlacement="tabsPlacement">
        <div class="tabbar" role="tablist" ref="tabbar">
            <slot></slot>
            <div ref="tabHighlight"
                 class="tab-highlight"
                 :class="{'animate':canTabHighlightAnimate}"
                 :style="tabHighlightStyle"></div>
        </div>
    </article>
</template>
<style lang="scss" src="./style.scss"></style>
<script type="text/javascript">
  /**
   * @component Tabs
   * @description
   *
   * ## 大标签 / Tabs
   *
   * ### 概述
   *
   * Tabs使用场景类似于"栏目切换", Tab组件中传入`props`定义每个tab的结构/样式/路由位子等信息。
   *
   * ### 使用规则
   *
   * 1. 页面结构参考下面示例
   * 2. 页面进入究竟激活那个子页面由路由规则决定
   * 3. `tabsHighlight`特性只能在`md`模式下使用
   *
   *
   * @props {String=} color                        - 颜色
   * @props {String} [mode='ios']                  - 模式
   * @props {Boolean} [tabsHighlight='false']      - tab下面是否显示选中bar, 只在md模式生效
   * @props {String} [tabsLayout='icon-top']       - tabbar的样式,可选配置: icon-top, icon-start, icon-end, icon-bottom, icon-hide, title-hide.
   *
   *
   * @fires component:Tabs#onTabChange
   *
   * @demo #/tabs
   * @usage
   *
   * <Page>
   *    <Content>
   *        <router-view></router-view>
   *    </Content>
   *    <Footer>
   *        <Tabs tabsLayout="icon-top" @onTabChange="onTabChange" ref=tabs>
   *            <router-view></router-view>
   *            <Tab slot="tab" :to="{name:'tabsBottom.demoTab1'}" tabBadge="13" tabTitle="User" tabBadgeStyle="danger" tabIcon="person"></Tab>
   *            <Tab slot="tab" :to="{name:'tabsBottom.demoTab2'}" tabBadge="2" tabTitle="Cars" tabBadgeStyle="dark" tabIcon="car"></Tab>
   *            <Tab slot="tab" :to="{name:'tabsBottom.demoTab3'}" tabBadge="7" tabTitle="Star" tabIcon="star" :enabled="true"></Tab>
   *        </Tabs>
   *    </Footer>
   * </Page>
   * ...
   * computed: {
   *   tabsComponent () {
   *    // 获取tabs的实例
   *    return this.$refs.tabs
   *   }
   * },
   * methods: {
   *    onTabChange(index){
   *      console.debug('事件 -> onTabChange-selectedIndex:' + index);
   *      console.debug('当前选择index的tab实例:')
   *      console.debug(this.tabsComponent.getByIndex(index))
   *      console.debug('获取当前在激活状态的tab实例:')
   *      console.debug(this.tabsComponent.getSelected())
   *      console.debug('由Tabs组件获取当前激活的index:' + this.tabsComponent.getActivatedIndex());
   *      console.debug('3s后选择第一个')
   *      clearTimeout(this.timer)
   *      this.timer = setTimeout(()=>{
   *        this.tabsComponent.select(0)
   *      },3000)
   *    },
   *  },
   *
   * */
  import { isNumber } from '../../util/type'
  import css from '../../util/get-css'

  export default {
    name: 'Tabs',
    props: {
      mode: {
        type: String,
        default () { return this.$config && this.$config.get('mode') || 'ios' }
      },
      color: String,
      tabsHighlight: {
        type: Boolean,
        default () { return this.$config && this.$config.getBoolean('tabsHighlight', false) }
      },
      // tabbar的样式: icon和title的放置位置
      tabsLayout: {
        type: String,
        default: 'icon-top',
        validator (val) {
          return ~[
            'icon-top',
            'icon-start',
            'icon-end',
            'icon-bottom',
            'icon-hide',
            'title-hide'
          ].indexOf(val)
        }
      }
    },
    data () {
      return {
        tabHighlightStyle: {},
        canTabHighlightAnimate: false,
        selectedIndex: -1 // 内部使用的, 表示当前处于激活的Tab的index
      }
    },
    inject: {
      headerComponent: {
        from: 'headerComponent',
        default: null
      },
      footerComponent: {
        from: 'footerComponent',
        default: null
      }
    },
    watch: {
      $route () {
        let _index = this.getActivatedIndex()
        if (this.selectedIndex !== _index) {
          this.selectedIndex = _index
          this.$_tabHighlightSelect(this.selectedIndex)
          /**
           * @event component:Tabs#onTabChange
           * @description tabs切换触发
           * @property {string}
           */
          this.$emit('onTabChange', _index)
        }
      }
    },
    computed: {
      // top, bottom
      tabsPlacement () {
        if (this.headerComponent) {
          return 'top'
        } else {
          return 'bottom'
        }
      },
      // 环境样式
      modeClass () {
        return `tabs-${this.mode}`
      },
      // 颜色
      colorClass () {
        return this.color ? (`tabs-${this.mode}-${this.color}`) : ''
      },
      // TabHighlight元素
      tabHighlightEle () {
        return this.$refs.tabHighlight
      },
      // 获取tabbar元素
      tabSlots () {
        return this.$slots.default.filter((item) => !!item.tag)
      },
      // 每个tab的宽度, 用于tabHighLight
      tabElementWidth () {
        return this.$refs.tabbar.offsetWidth / this.tabSlots.length || 0
      }
    },
    provide () {
      const _this = this
      return {
        tabsComponent: _this
      }
    },
    methods: {
      /**
       * @function getByIndex
       * @description
       * 获取第几个index的Tab组件实例
       * @param {number} index - 第几个index
       * @return {Tab}
       * */
      getByIndex (index) {
        return this.tabSlots[index] && this.tabSlots[index].componentInstance
      },

      /**
       * @function getActivatedIndex
       * @description
       * 获取当前被选中Tab实例的index
       * @return {number}
       * */
      getActivatedIndex () {
        for (let i = 0, len = this.tabSlots.length; len > i; i++) {
          let tabIns = this.tabSlots[i].componentInstance
          if (tabIns.$_isMatch()) {
            return i
          }
        }
        return 0
      },

      /**
       * @function getSelected
       * @description
       * 获取当前选中的Tab实例
       * @return {Tab}
       * */
      getSelected () {
        return this.getByIndex(this.getActivatedIndex())
      },

      /**
       * @function select
       * @description
       * 根据传入值选中Tab
       * @param {Number/Tab} tabOrIndex - 传入的Tab实例或者Tab的序号
       * @return {Tab}
       * */
      select (tabOrIndex) {
        let tabIns = tabOrIndex
        if (isNumber(tabOrIndex)) {
          tabIns = this.getByIndex(tabOrIndex)
        }

        if (tabIns === this.getSelected()) {
          return tabIns
        } else {
          this.$router[tabIns.routerType](tabIns.to)
          return tabIns
        }
      },

      /**
       * 第一次进入是的初始化
       * @private
       */
      $_initTabs () {
        this.selectedIndex = this.getActivatedIndex()
        // 激活当前选中的Highlight
        if (this.tabsHighlight && this.mode === 'md') {
          this.$_tabHighlightSelect(this.selectedIndex)
        }
      },

      /**
       * 下滑线定位
       * @param {number} index
       * @private
       */
      $_tabHighlightSelect (index) {
        let _offsetLeft = this.tabElementWidth * index
        let transform = `translate3d(${_offsetLeft}px,0,0) scaleX(${this.tabElementWidth})`

        this.tabHighlightStyle = {
          [css.transform]: transform
        }

        // wait for style set
        window.setTimeout(() => {
          this.canTabHighlightAnimate = true
        }, 16)
      }
    },
    mounted () {
      this.$_initTabs()
    }
  }
</script>