vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > vue usePop弹窗控制器

vue usePop弹窗控制器的实现

作者:copyLeft

本文主要介绍了vue usePop弹窗控制器的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

当UI库弹窗无法满足自定义需求时,需要我们自己开发简单的弹窗组件。弹窗组件与普通业务组件开发没有太大区别,重点在多弹窗之间的关系控制。例如: 弹窗1,弹窗2 由于触发时机不同,需要不同的层叠关系,后触发的始终在最前端,点击弹窗头改变层叠关系。 单一弹窗多处调用等。这里封装基础的管理钩子,简化这些问题的处理。

功能目标

该钩子的目的主要为了处理弹窗之间的控制关系,具体如何渲染交由调用方

快速使用

// 主容器
import { usePopContainer, buildDefaultPopBind, position } from '@/hooks/usePop'
import UserInfoPop form './UserInfoPop.vue'
// 快捷工具,将内部钩子通过依赖注入,共享给子组件
const [popMap, popTools] = usePopContainer()
const popBind = buildDefaultPopBind(popTools, popTools.componentsCache)


const userPop = popBind('userInfo', UserInfoPop, {
  position: { // 组件定位
    top: 200
  },
  userId: 'xxx', // 组件porps
  @close(){ // 组件事件
    console.log('close')
  }
})


// 调用
userPop.open()
setTimeout(userPop.close, 1000 * 3)




// template
<template v-for="(pop, popId) of popMap">
  // 渲染弹窗列表
   <component
     :is="pop.component"
     :key="popId"
     v-bind="pop.props"
     v-on="pop.on"
   >
   </component>
</template>

多处调用

同一弹窗,多实例 add

// 容器注册
const [popMap, popTools] = usePopContainer()
// 新增弹窗1
popTools.add(popId1, {
   component: UserPop, // 弹窗组件
   useId: 'xxx', // 弹窗Props
   '@close': () => { ... } //弹窗事件
})


// 新增弹窗2
popTools.add(popId2, {
   component: UserPop, // 弹窗组件
   useId: 'xxx', // 弹窗Props
   '@close': () => { ... } //弹窗事件
})
// 覆盖弹窗1
// popId 为弹窗唯一标识, 如果popId相同,组件配置将被替换
popTools.add(popId1, {
   component: UserPop, // 弹窗组件
   useId: 'yyy', // 弹窗Props
   '@close': () => { ... } //弹窗事件
})

所有弹窗都通过popId,查找or判断唯一性。

配置参数:以@ 开头的都将组为组件的事件被绑定, 除了 @[事件名] component 其他属性都将作为props,包括 style 等属性

移除 remove

const [popMap, popTools] = usePopContainer()
popTools.add(popId, {
   component: UserPop, // 弹窗组件
   useId: 'xxx', // 弹窗Props
   '@close': () => { ... } //弹窗事件
})
// 移除
popTools.remove(popId)

替换 replace

// 主容器
const [popMap, popTools] = usePopContainer()


// 子组件A
popTools.replace(popId, {
   component: UserPop, // 弹窗组件
   useId: 'xxx', // 弹窗Props
   '@close': () => { ... } //弹窗事件
})


// 子组件B
popTools.replace(popId, {
   component: UserPop, // 弹窗组件
   useId: 'xxx', // 弹窗Props
   '@close': () => { ... } //弹窗事件
})

当有多处调用同一弹窗,而只需要最新的触发弹窗时,使用 replace. 该方法其实就是 remove add 的包装方法

更新 update

const [popMap, popTools] = usePopContainer()
popTools.replace(popId, {
   component: UserPop, // 弹窗组件
   useId: 'xxx', // 弹窗Props
   '@close': () => { ... } //弹窗事件
})
// 更新参数
popTools.update(popId, {
   useId: 'yyy'

})

通过popId 查询弹窗,将新传入的参数与原配置做合并

预注册 componentsCache

const [popMap, popTools] = usePopContainer()
// 局部预先注册
// 注册只是预先缓存
popTools.componentsCache.add(popId, {
  component: UserPop,
  id: 'xxx',
  '@cloes': () => {...}
})
// 调用
popTools.add(popId, { component: popId })
// of
popTools.replace(popId, { component: popId })
// add将从componentsCache查询预注册配置

除了局部缓存, componentsCache, 模块还导出了 globalComponentsCache 全局公共缓存。

依赖注入

为了方便父子组件调用,提供了usePopContainer usePopChildren 方法,

// 父组件
const [popMap, popTools] = usePopContainer()
// 子组件
const { popTools } = usePopChildren()
popTools.add({
   ...
})

函数接收依赖注入标识, 为传入标识时,使用默认标识

usePop 工具函数

 使用该函数,多个弹窗公用相同的popId

core 实现

import { shallowRef, unref, provide, inject } from 'vue'
import { merge } from 'lodash-es'
import { splitProps, counter } from './utils'


export const DEFAULT_POP_SIGN = 'DEFAULT_POP_SIGN'


// 全局层级累加器
export const counterStore = counter()


/**
 * 预先pop注册表
 * @summary
 * 便捷多处pop调用, 调用pop显示方法时,
 * 直接通过名称查询对应的组件预设
 * 将调用与事件配置解耦
 * @returns
 */
function componentsRegistry () {
  let componentsCache = new Map([])


  function has (componentName) {
    return componentsCache.has(componentName)
  }


  function add (componentName, options) {
    componentsCache.set(componentName, options)
  }


  function remove (componentName) {
    if (has(componentName)) {
      componentsCache.delete(componentName)
    }
  }


  function fined (componentName) {
    return componentsCache.get(componentName)
  }


  function clear () {
    componentsCache = new Map([])
  }


  function getComponents () {
    return [...componentsCache.values()]
  }


  function getComponentNames () {
    return [...componentsCache.keys()]
  }


  return {
    has,
    add,
    remove,
    fined,
    clear,
    getComponents,
    getComponentNames
  }
}


export const globalComponentsCache = componentsRegistry()


/**
 * 弹窗控制器
 * @summary
 * 提供多弹窗控制逻辑:
 * 1. 单例, 多例: 通过不同的 popId 控制弹窗实例的个数
 * 2. 参数接收: open接收初始传给pop的事件和参数配置, update 提供参数更新
 * 3. 事件回调: options 配置属性 { @[事件名称]:事件回调 } 将作为事件绑定到pop上
 * 4. 动态叠加: 内部将为组件配置 zIndex, 组件内需要自定义接收该参数,判断如何处理层叠关系
 * 5. 定位: 定位需要弹窗组件接收 position props 内部绑定样式
 *
 * @tips
 *  这里定位为了兼容 useMove做了接口调整,原接口直接输出定位样式。当前出position属性,
 *  组件内需要自行处理定位样式。 这里存在 style 合并和透传的的问题, 通过透传的style与
 *  props 内定义的style将分开处理, 即最终的结果时两个style的集合, 且透传的style优先级高于
 *  prop。所以如果直出定位样式,通过透传绑定给弹窗组件,后续的useMove拖拽样式将始终被透传样式覆盖
 *
 * @api
 * - add(popId, options) 创建弹窗
 * - update(popId, options) 更新弹窗配置(定位, props,events)
 * - remove(popId) 移除弹窗
 * - replace(popId, options) 替换,如果多处调用同一弹窗,希望只显示唯一同类弹窗时,
 *  使用该函数,多个弹窗公用相同的popId
 * - clearAllPop() 清空所有弹窗
 * - updateIndex(popId) 更新弹窗层级
 * - downIndex(popId) 层级下降一级
 * - topIndex(popId) 层级置顶
 *
 * @example01 - 一般使用
 *
 * const [
 *  pops,
 *  popTools
 * ]  = usePop()
 *
 *
 * // 容器组件
 * <component
 *  v-for='(pop, popId) of pops'
 *  :is='pop'
 *  v-bind='pop.props' // 接收定位样式
 *  v-on='pop.on' // 接收回调事件
 *  :key='popId'>
 * </component>
 *
 * // 调用弹窗
 * popTools.add('popId', {
 *  component: POP, // 弹窗组件
 *  position: { top: 200 } // 弹窗定位
 *  title: 'xxx', // 弹窗自定义props
 *  @click(e){  // 弹窗事件
 *     ....
 *  }
 * })
 *
 *
 * @example02 - 预注册
 * 通过预注册组件,再次调用时,只需要传入对应注册名称,而不需要具体的配置项
 * const [ pops, popTools ] = usePop()
 *
 * // 注册本地弹窗
 * popTools.componentsCache.add('userInfo', {
 *  component: CMP,
 *  opsition: { ... }
 *  ...
 * })
 *
 * // 调用
 * popTools.add('userInfo', { component: 'userInfo' })
 *
 */
export function usePop () {
  const components = shallowRef({})
  const componentsCache = componentsRegistry()


  function has (popId) {
    return !!unref(components)[popId]
  }


  /**
   * 添加pop
   * @param popId
   * @param options
   * @returns
   */
  function add (popId, options = {}) {
    if (has(popId)) {
      return false
    }


    let {
      component,
      ..._options
    } = options


    // 全局缓存
    if (globalComponentsCache.has(component)) {
      const { component: cacheComponents, ...cacheOptions } = globalComponentsCache.fined(component)
      component = cacheComponents
      _options = { ...cacheOptions, ..._options }
    }


    // 局部缓存
    if (componentsCache.has(component)) {
      const { component: cacheComponents, ...cacheOptions } = componentsCache.fined(component)
      component = cacheComponents
      _options = { ...cacheOptions, ..._options }
    }


    counterStore.add()
    const newOptions = splitProps({ ..._options, zIndex: counterStore.getCount() })


    components.value = {
      ...components.value,
      [popId]: {
        popId,
        component,
        ...newOptions
      }
    }
  }


  /**
   * 更新组件参数
   * @param {*} popId
   * @param {*} options
   * @returns
   */
  function update (popId, options = {}) {
    if (!has(popId)) {
      return false
    }


    const { component, ...oldOptions } = components.value[popId]
    const newOptions = splitProps(options)
    components.value = {
      ...components.value,
      [popId]: {
        component,
        ...merge(oldOptions, newOptions)
      }
    }
  }


  /**
   * 移除pop
   * @param popId
   */
  function remove (popId) {
    if (has(popId)) {
      const newCmp = components.value
      delete newCmp[popId]
      components.value = {
        ...newCmp
      }
    }
  }


  /**
   * 多处调用同一pop时, 替换原显示pop。
   * @param popId
   * @param options
   */
  function replace (popId, options) {
    remove(popId)
    add(popId, options)
  }


  function clearAllPop () {
    components.value = {}
  }


  /**
  * 向上一层级
  * @param popId
  * @returns
  */
  function updateIndex (popId) {
    if (!has(popId)) {
      return
    }
    const currentComponent = unref(components)[popId]
    const upComponent = Object.values(unref(components)).fined(i => i.zIndex > currentComponent.zIndex)
    const currentIndex = currentComponent.zIndex
    const upIndex = upComponent.zIndex
    update(currentIndex.popId, {
      zIndex: upIndex
    })
    update(upComponent.popId, {
      zIndex: currentIndex
    })
  }


  /**
   * 向下一层级
   * @param {*} popId
   * @returns
   */
  function downIndex (popId) {
    if (!has(popId)) {
      return
    }
    const currentComponent = unref(components)[popId]
    const upComponent = Object.values(unref(components)).fined(i => i.zIndex < currentComponent.zIndex)
    const currentIndex = currentComponent.zIndex
    const upIndex = upComponent.zIndex
    update(currentIndex.popId, {
      zIndex: upIndex
    })
    update(upComponent.popId, {
      zIndex: currentIndex
    })
  }


  /**
   * 顶层
   * @param popId
   * @returns
   */
  function topIndex (popId) {
    if (!has(popId)) {
      return
    }
    counterStore.add()
    update(popId, {
      zIndex: counterStore.getCount()
    })
  }


  return [    components,    {      has,      add,      remove,      update,      replace,      clearAllPop,      topIndex,      updateIndex,      downIndex,      componentsCache    }  ]
}


/**
 * 嵌套结构下的弹窗钩子
 */


// 容器钩子
export function usePopContainer (provideKey = DEFAULT_POP_SIGN) {
  const [popMap, popTools] = usePop()
  provide(
    provideKey,
    {
      popTools
    }
  )
  return [    popMap, popTools  ]
}


// 子容器钩子
export function usePopChildren (provideKey = DEFAULT_POP_SIGN) {
  return inject(provideKey, {})
}

core utils

export function isEvent (propName) {
  const rule = /^@/i
  return rule.test(propName)
}

// @click => click
export function eventNameTransition (name) {
  return name.replace('@', '')
}

// 拆分事件与属性
export function splitProps (cmpProps) {
  return Object.entries(cmpProps).reduce((acc, [propName, propValue]) => {
    if (isEvent(propName)) {
      // 自定义事件
      acc.on[eventNameTransition(propName)] = propValue
    } else {
      acc.props[propName] = propValue
    }

    return acc
  }, { on: {}, props: {} })
}

export function counter (initCount = 0, step = 1) {
  let count = initCount

  function add (customStep) {
    count += customStep || step
  }

  function reduce (customStep) {
    count -= customStep || step
  }

  function reset (customStep) {
    count = customStep || initCount
  }

  function getCount () {
    return count
  }

  return {
    add,
    reduce,
    reset,
    getCount
  }
}

工具

import { merge } from 'lodash-es'

/**
 * 注册并返回弹窗快捷方法
 * @param {*} popTools
 * @returns
 */
export function buildDefaultPopBind (popTools, componentsCache) {
  return (popId, component, options) => {
    componentsCache.add(popId, {
      component,
      // 默认定位
      position: position(),
      ...bindDefaultEvents(popTools, popId),
      ...options
    })


    return {
      open (options) {
        popTools.add(popId, { component: popId, ...options })
      },
      close () {
        popTools.remove(popId)
      },
      update (options) {
        popTools.update(popId, { component: popId, ...options })
      },
      replace (options) {
        popTools.replace(popId, { component: popId, ...options })
      }
    }
  }
}


export const DEFAULT_POSITION = {
  top: 240,
  left: 0,
  right: 0
}


export function position (options = DEFAULT_POSITION) {
  return merge({}, DEFAULT_POSITION, options)
}


export function bindDefaultEvents (popTools, popId) {
  return {
    '@headerMousedown' () {
      popTools.topIndex(popId)
    },
    '@close' (e) {
      popTools.remove(popId)
    }
  }
}

 到此这篇关于vue usePop弹窗控制器的实现的文章就介绍到这了,更多相关vue usePop弹窗控制器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文