这篇文章主要介绍“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>
// 容器注册 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 等属性
const [popMap, popTools] = usePopContainer() popTools.add(popId, { component: UserPop, // 弹窗组件 useId: 'xxx', // 弹窗Props '@close': () => { ... } //弹窗事件 }) // 移除 popTools.remove(popId)
// 主容器 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
的包装方法
const [popMap, popTools] = usePopContainer() popTools.replace(popId, { component: UserPop, // 弹窗组件 useId: 'xxx', // 弹窗Props '@close': () => { ... } //弹窗事件 }) // 更新参数 popTools.update(popId, { useId: 'yyy' })
通过popId 查询弹窗,将新传入的参数与原配置做合并
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({ ... })
函数接收依赖注入标识, 为传入标识时,使用默认标识
add(popId, options) 创建弹窗
update(popId, options) 更新弹窗配置(定位, props,events)
remove(popId) 移除弹窗
replace(popId, options) 替换,如果多处调用同一弹窗,希望只显示唯一同类弹窗时,
使用该函数,多个弹窗公用相同的popId
clearAllPop() 清空所有弹窗
updateIndex(popId) 更新弹窗层级
downIndex(popId) 层级下降一级
topIndex(popId) 层级置顶
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, {}) }
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弹窗控制器如何实现”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注亿速云行业资讯频道,小编每天都会为大家更新不同的知识点。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。