温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

vue usePop弹窗控制器如何实现

发布时间:2023-04-17 15:31:47 来源:亿速云 阅读:152 作者:iii 栏目:开发技术

这篇文章主要介绍“vue usePop弹窗控制器如何实现”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“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 工具函数

    • add(popId, options) 创建弹窗

    • update(popId, options) 更新弹窗配置(定位, props,events)

    • remove(popId) 移除弹窗

    • replace(popId, options) 替换,如果多处调用同一弹窗,希望只显示唯一同类弹窗时,

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

    • clearAllPop() 清空所有弹窗

    • updateIndex(popId) 更新弹窗层级

    • downIndex(popId) 层级下降一级

    • topIndex(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弹窗控制器如何实现”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注亿速云行业资讯频道,小编每天都会为大家更新不同的知识点。

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    vue
    AI