前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >vue usePop弹窗控制器

vue usePop弹窗控制器

作者头像
copy_left
发布2022-12-05 15:51:14
5960
发布2022-12-05 15:51:14
举报
文章被收录于专栏:方球

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

功能目标

  • 单例,多例弹窗
  • 可配置弹窗自定义参数
  • 可接收弹窗自定义事件
  • 层级控制
  • 自定义定位

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

快速使用

代码语言:javascript
复制
// 主容器
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

代码语言:javascript
复制
// 容器注册
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

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

替换 replace

代码语言:javascript
复制
// 主容器
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

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

})

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

预注册 componentsCache

代码语言:javascript
复制
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 方法,

代码语言:javascript
复制
// 父组件
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 实现

代码语言:javascript
复制
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, {})
}

工具

代码语言:javascript
复制
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)
    }
  }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 功能目标
  • 快速使用
  • 多处调用
    • 同一弹窗,多实例 add
      • 移除 remove
        • 替换 replace
          • 更新 update
            • 预注册 componentsCache
            • 依赖注入
            • usePop 工具函数
            • core 实现
            • 工具
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档