app/app.vue

<template>
  <div class="ion-app" :version="version" :class="[modeClass,{'disable-scroll':isScrollDisabled}]">
    <div class="app-viewport"></div>
    <div class="app-root">
      <slot></slot>
    </div>
    <div id="modal-portal"></div>
    <div id="sheet-portal"></div>
    <div id="alert-portal"></div>
    <div id="loading-portal"></div>
    <div id="toast-portal"></div>
    <!--当页面被点击的时候,防止在动画的过程中再次点击页面导致bug的蒙层,全局最高!z-index=99999-->
    <div class="click-block" :class="{'click-block-enabled':isClickBlockEnabled}"></div>
  </div>
</template>

<script type="text/javascript">
import { isString, isPresent } from '../../util/util'
import { setElementClass } from '../../util/dom'
import ModeMixins from '../../themes/theme.mixins'
import ClickBlock from './click-block'

const CLICK_BLOCK_BUFFER_IN_MILLIS = 64 // click_blcok等待时间
const CLICK_BLOCK_DURATION_IN_MILLIS = 700 // 时间过后回复可点击状态
const clickBlockInstance = new ClickBlock()

let scrollDisTimer = null // 计时器
export default {
  name: 'vm-app',
  mixins: [ModeMixins],
  provide () {
    let _this = this
    return {
      appComponent: _this
    }
  },
  data () {
    return {
      // ----------- App -----------
      disabledTimeRecord: 0, // 禁用计时
      scrollTimeRecord: 0, // 滚动计时
      isScrollDisabled: false, // 控制页面是否能滚动
      isClickBlockEnabled: false, // 控制是否激活 '冷冻'效果 click-block-enabled

      isScrolling: false, // 可滚动状态
      isEnabled: true, // 可点击状态

      version: isPresent(window.VM) && window.VM.version
    }
  },
  computed: {
    modeClass () {
      return `app-root app-root-${this.mode} ${this.mode} platform-${this.mode}`
    }
  },
  created () {
    console.assert(this.$platform, `The Component of <App> need 'platform' instance`)
    console.assert(this.$config, `The Component of <App> need 'config' instance`)

    /**
     * $app对外方法
     */
    let proto = Reflect.getPrototypeOf(Reflect.getPrototypeOf(this))
    proto.$app = this

    const _this = this
    this.$events.$on('onScrollStart', () => {
      _this.isScrolling = true
    })
    this.$events.$on('onScroll', () => {
      _this.isScrolling = true
    })
    this.$events.$on('onScrollEnd', () => {
      _this.isScrolling = false
    })

    // 设置当前可点击
    this.isClickBlockEnabled = true
  },
  mounted () {
    if (window.VM) {
      window.VM.$app = this
      // 用于判断组件是否在VM的组件树中
      window.VM.$events = this.$events
    }
  },
  methods: {
    /**
     * @function setEnabled
     * @description
     * 设置当前页面是否能点击滑动, 一般使用在像ActionSheet/Alert/Modal等弹出会出现transition动画,
     * 当transition动画进行中,页面是锁定的不能点击,因此使用该函数设定App的状态, 保证动画过程中, 用户不会操作页面
     * @param {boolean} isEnabled  - `true` for enabled, `false` for disabled
     * @param {number} duration - isEnabled=false的过期时间 当 `isEnabled` 设置为`false`, 则duration之后,`isEnabled`将自动设为`true`
     *
     * @example
     * this.$app && this.$app.setEnabled(false, 400) -> 400ms内页面不可点击, 400ms过后可正常使用
     * this.$app && this.$app.setEnabled(true) -> 64ms后解除锁定
     **/
    setEnabled (isEnabled, duration = CLICK_BLOCK_DURATION_IN_MILLIS) {
      this.disabledTimeRecord = isEnabled ? 0 : Date.now() + duration
      this.isEnabled = isEnabled
      if (isEnabled) {
        // disable the click block if it's enabled, or the duration is tiny
        clickBlockInstance
          .activate(false, CLICK_BLOCK_BUFFER_IN_MILLIS)
          .then(() => {
            this.isEnabled = true
          })
      } else {
        // show the click block for duration + some number
        clickBlockInstance
          .activate(true, duration + CLICK_BLOCK_BUFFER_IN_MILLIS)
          .then(() => {
            this.isEnabled = true
          })
      }
    },

    /**
     * @function setDisableScroll
     * @description
     * 是否点击滚动, 这个需要自己设置时间解锁
     * @param {Boolean} isScrollDisabled - 是否禁止滚动点击 true:禁止滚动/false:可以滚动
     * @param {number} duration - 时间过后则自动解锁
     * @example
     * this.$app && this.$app.setDisableScroll(true, 400) -> 400ms内页面不可滚动, 400ms过后可正常使用
     * this.$app && this.$app.setDisableScroll(false) ->立即解除锁定
     */
    setDisableScroll (isScrollDisabled, duration = 0) {
      if (duration > 0 && isScrollDisabled) {
        this.isScrollDisabled = isScrollDisabled
        window.clearTimeout(scrollDisTimer)
        scrollDisTimer = window.setTimeout(() => {
          this.isScrollDisabled = false
        }, duration)
      }
    },

    /**
     * @function setClass
     * @description
     * 设置根组件的class样式, 比如全局颜色替换或者结构变更
     * @param {string} className - 样式名称
     * @param {boolean} [isAdd=false] - 是否添加
     */
    setClass (className, isAdd = false) {
      if (className) {
        setElementClass(this.$el, className, isAdd)
      }
    },

    /**
     * @function setDocTitle
     * @param {String|Object}  _title - 设置标题
     * @param {String}  _title.title - 标题
     * @description
     * 设置document.title的值, 如果传入的是string, 则为title的字符串, 如果是对象, 则title字段为标题名称
     */
    setDocTitle (_title) {
      if (isString(_title)) {
        _title = { title: _title }
      }
      // BugFixed: 如果组件不是通过异步加载, 则他的执行顺序会很靠前, 此时平台的方法并未初始化完毕. 因此异步定时后在执行
      window.setTimeout(() => {
        let isHandled =
          !!this.$platform &&
          !!this.$platform.setNavbarTitle &&
          this.$platform.setNavbarTitle(_title)
        if (!isHandled) {
          if (this.$platform && this.$platform.platforms().length <= 2) {
            // PC端
            document.title = _title.title || ''
          } else {
            // 利用iframe的onload事件刷新页面
            document.title = _title.title
            let iframe = document.createElement('iframe')
            // 空白图片
            iframe.src = 'data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=='
            iframe.style.visibility = 'hidden'
            iframe.style.width = '1px'
            iframe.style.height = '1px'
            iframe.onload = function () {
              window.setTimeout(function () {
                document.body.removeChild(iframe)
              }, 0)
            }
            document.body.appendChild(iframe)
          }
        }
      }, 16 * 5)
    }

  }
}
</script>

<style lang="scss">
@import "app";
@import "app.ios";
@import "app.md";

// Page Animate
@import "../../transition/app";
</style>