components/toast/toast.js

/**
 * @component Toast
 * @description
 *
 * ## 弹出层 / Toast提示组件
 *
 * Toast是移动端比较灵活的通知组件, 可以用它来处理反馈信息或者展示系统消息. Toast组件可以出现在内容的上面/下面/中间, 可以定时关闭, 也可以手动点击Toast的关闭按钮, 形式较为灵活.
 *
 * ### 还有
 *
 * Toast组件本身并不是单例对象, 但是调用指纹都为`Toast('Toast Bottom Only String')`, 因此可能会问:
 * 如何程序关闭toast呢? 目前, 关闭Toast只有三个方法:
 *
 * 1. duration 过期时间
 * 2. showCloseButton 关闭按钮
 * 3. 当上面两个都设置了, 则只执行第二个配置
 * 4. `let toast = Toast('Toast Bottom Only String')`返回toast实例, 执行`toast.dismiss()`执行关闭
 *
 * 因此使用Toast需要知道这一点.
 *
 *
 * ### 此外
 *
 * - 在Toast的传入参数中定义`onDismiss`回调函数, 可以在Toast关闭及动画结束后可以进行一些操作.
 * - `dismissOnPageChange`表示如果路由切换自动关闭Toast, 这个属性默认值为`false`.
 *
 * ### 实例化Toast的方法
 *
 * #### 1. 只传入Message
 *
 *
 *```
 Toast('Bottom was added successfully')
 *```
 *
 * #### 2. 传入Message和Duration
 *
 *```
 Toast('Bottom was added successfully',1000)
 *```
 *
 * #### 3. 传入Options对象
 *
 *```
 Toast({
    message: 'Bottom was added successfully',
    duration: 3000,
    position: 'bottom',
    dismissOnPageChange: true,
    onDismiss () {
      console.debug('2 onDismiss in promise success!')
    }
 })
 Toast({
    message: 'Top with Button was added successfully',
    duration: 3000, // 这个不生效
    position: 'top',
    showCloseButton: true,
    closeButtonText: '好的',
    cssClass: 'showCloseBtnToastCssClass',
    dismissOnPageChange: true,
    onDismiss () {
      console.debug('5 onDismiss in promise success!')
    }
 })
 *```
 *
 * @props {string} message - Toast显示的message, 如果文本过程则折行并自动撑开容器
 * @props {number} [duration=3000] - Toast开启时间, 过期后关闭
 * @props {string} [position="bottom"] - Toast开启放置的位置. 可以是: "top", "middle", "bottom".
 * @props {string} [cssClass] - 额外的样式定义, 多个样式使用空格隔开
 * @props {boolean} [showCloseButton=false] - 是否显示关闭按钮
 * @props {string} [closeButtonText='Close'] - 关闭按钮的文字, 这里默认是Close
 * @props {boolean} [dismissOnPageChange=false] - 当导航切换时, 是否自动关闭, 默认不关闭
 * @props {string} [mode='ios'] - 模式
 * @props {string} [onDismiss=noop] - 当关闭动画结束时执行的函数
 * @props {string} [type=''] - (支付宝/DingTalk) toast 类型,展示相应图标,类型可选值: success / fail
 *
 * @demo #/toast
 */

import Vue from 'vue'
import { isObject, isString } from '../../util/util'
import ToastComponent from './toast.vue'

const Toast = Vue.extend(ToastComponent)
const DOM_INSERT_POSITION = 'toastPortal' // 插入的DOM位置
const DOM_INSERT_POSITION_FALLBACK = 'app' // fallback选项

/**
 * 创建ToastInstance, 并且根据传参指纹构建对象
 * @param {any} arguments - 传入参数
 * @private
 * */
function ToastFactory () {
  let _args = Array.from(arguments)
  let propsData = {}

  // get el position
  let _insertPosition =
    document.getElementById(DOM_INSERT_POSITION) ||
    document.getElementById(DOM_INSERT_POSITION_FALLBACK) ||
    document.body
  let el = _insertPosition.appendChild(document.createElement('div'))

  if (_args.length === 2) {
    propsData = {
      message: _args[0],
      duration: _args[1]
    }
  }

  if (_args.length === 1 && isString(_args[0])) {
    propsData = {
      message: _args[0]
    }
  }

  if (_args.length === 1 && isObject(_args[0])) {
    propsData = _args[0]
  }

  let h5Toast = new Toast({ el, propsData: propsData })
  return {
    present () {
      return new Promise(resolve => {
        let isHandled =
          !propsData.isH5 &&
          window.VM &&
          window.VM.platform &&
          window.VM.platform.showToast &&
          window.VM.platform.showToast(propsData)
        if (isHandled) {
          resolve()
        } else {
          console.debug('Toast:present 组件使用H5模式!')
          h5Toast.present().then(() => {
            resolve()
          })
        }
      })
    },
    dismiss () {
      return new Promise(resolve => {
        window.VM &&
          window.VM.platform &&
          window.VM.platform.hideToast &&
          window.VM.platform.hideToast()
        h5Toast.dismiss().then(() => {
          resolve()
        })
      })
    }
  }
}

/**
 * 对外的Toast构建部分
 * @function present
 * @description 打开Toast
 * @param {object} args - 传入参数
 * @param {string} args.message - The message for the toast. Long strings will wrap and the toast container will
 *   expand.
 * @param {number} [args.duration=3000] - How many milliseconds to wait before hiding the toast.
 * @param {string} [args.position="bottom"] - The position of the toast on the screen. Accepted values: "top",
 *   "middle", "bottom".
 * @param {string} [args.cssClass] - Additional classes for custom styles, separated by spaces.
 * @param {boolean} [args.showCloseButton=false] - Whether or not to show a button to close the toast.
 * @param {string} [args.closeButtonText='Close'] - Text to display in the close button.
 * @param {boolean} [args.dismissOnPageChange=false] - Whether to dismiss the toast when navigating to a new page or
 *   nav back.
 * @return {ToastInstance} 返回Toast的实例
 * @private
 * */
export default function (...args) {
  let _instance = ToastFactory(...args)
  // 自动开启
  _instance.present()
  return _instance
}