阅读 86

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

工具

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)     }   } }


作者:copyLeft
链接:https://juejin.cn/post/7171643239370326053


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐