vue usePop彈窗控制器的實現(xiàn)
當(dāng)UI庫彈窗無法滿足自定義需求時,需要我們自己開發(fā)簡單的彈窗組件。彈窗組件與普通業(yè)務(wù)組件開發(fā)沒有太大區(qū)別,重點在多彈窗之間的關(guān)系控制。例如: 彈窗1,彈窗2 由于觸發(fā)時機不同,需要不同的層疊關(guān)系,后觸發(fā)的始終在最前端,點擊彈窗頭改變層疊關(guān)系。 單一彈窗多處調(diào)用等。這里封裝基礎(chǔ)的管理鉤子,簡化這些問題的處理。
功能目標(biāo)
- 單例,多例彈窗
- 可配置彈窗自定義參數(shù)
- 可接收彈窗自定義事件
- 層級控制
- 自定義定位
該鉤子的目的主要為了處理彈窗之間的控制關(guān)系,具體如何渲染交由調(diào)用方
快速使用
// 主容器
import { usePopContainer, buildDefaultPopBind, position } from '@/hooks/usePop'
import UserInfoPop form './UserInfoPop.vue'
// 快捷工具,將內(nèi)部鉤子通過依賴注入,共享給子組件
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')
}
})
// 調(diào)用
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>
多處調(diào)用
同一彈窗,多實例 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 為彈窗唯一標(biāo)識, 如果popId相同,組件配置將被替換
popTools.add(popId1, {
component: UserPop, // 彈窗組件
useId: 'yyy', // 彈窗Props
'@close': () => { ... } //彈窗事件
})
所有彈窗都通過popId,查找or判斷唯一性。
配置參數(shù):以
@開頭的都將組為組件的事件被綁定, 除了@[事件名]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': () => { ... } //彈窗事件
})
當(dāng)有多處調(diào)用同一彈窗,而只需要最新的觸發(fā)彈窗時,使用 replace. 該方法其實就是
removeadd的包裝方法
更新 update
const [popMap, popTools] = usePopContainer()
popTools.replace(popId, {
component: UserPop, // 彈窗組件
useId: 'xxx', // 彈窗Props
'@close': () => { ... } //彈窗事件
})
// 更新參數(shù)
popTools.update(popId, {
useId: 'yyy'
})
通過popId 查詢彈窗,將新傳入的參數(shù)與原配置做合并
預(yù)注冊 componentsCache
const [popMap, popTools] = usePopContainer()
// 局部預(yù)先注冊
// 注冊只是預(yù)先緩存
popTools.componentsCache.add(popId, {
component: UserPop,
id: 'xxx',
'@cloes': () => {...}
})
// 調(diào)用
popTools.add(popId, { component: popId })
// of
popTools.replace(popId, { component: popId })
// add將從componentsCache查詢預(yù)注冊配置
除了局部緩存, componentsCache, 模塊還導(dǎo)出了 globalComponentsCache 全局公共緩存。
依賴注入
為了方便父子組件調(diào)用,提供了usePopContainer usePopChildren 方法,
// 父組件
const [popMap, popTools] = usePopContainer()
// 子組件
const { popTools } = usePopChildren()
popTools.add({
...
})
函數(shù)接收依賴注入標(biāo)識, 為傳入標(biāo)識時,使用默認(rèn)標(biāo)識
usePop 工具函數(shù)
- add(popId, options) 創(chuàng)建彈窗
- update(popId, options) 更新彈窗配置(定位, props,events)
- remove(popId) 移除彈窗
- replace(popId, options) 替換,如果多處調(diào)用同一彈窗,希望只顯示唯一同類彈窗時,
使用該函數(shù),多個彈窗公用相同的popId
- clearAllPop() 清空所有彈窗
- updateIndex(popId) 更新彈窗層級
- downIndex(popId) 層級下降一級
- topIndex(popId) 層級置頂
core 實現(xiàn)
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()
/**
* 預(yù)先pop注冊表
* @summary
* 便捷多處pop調(diào)用, 調(diào)用pop顯示方法時,
* 直接通過名稱查詢對應(yīng)的組件預(yù)設(shè)
* 將調(diào)用與事件配置解耦
* @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 控制彈窗實例的個數(shù)
* 2. 參數(shù)接收: open接收初始傳給pop的事件和參數(shù)配置, update 提供參數(shù)更新
* 3. 事件回調(diào): options 配置屬性 { @[事件名稱]:事件回調(diào) } 將作為事件綁定到pop上
* 4. 動態(tài)疊加: 內(nèi)部將為組件配置 zIndex, 組件內(nèi)需要自定義接收該參數(shù),判斷如何處理層疊關(guān)系
* 5. 定位: 定位需要彈窗組件接收 position props 內(nèi)部綁定樣式
*
* @tips
* 這里定位為了兼容 useMove做了接口調(diào)整,原接口直接輸出定位樣式。當(dāng)前出position屬性,
* 組件內(nèi)需要自行處理定位樣式。 這里存在 style 合并和透傳的的問題, 通過透傳的style與
* props 內(nèi)定義的style將分開處理, 即最終的結(jié)果時兩個style的集合, 且透傳的style優(yōu)先級高于
* prop。所以如果直出定位樣式,通過透傳綁定給彈窗組件,后續(xù)的useMove拖拽樣式將始終被透傳樣式覆蓋
*
* @api
* - add(popId, options) 創(chuàng)建彈窗
* - update(popId, options) 更新彈窗配置(定位, props,events)
* - remove(popId) 移除彈窗
* - replace(popId, options) 替換,如果多處調(diào)用同一彈窗,希望只顯示唯一同類彈窗時,
* 使用該函數(shù),多個彈窗公用相同的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' // 接收回調(diào)事件
* :key='popId'>
* </component>
*
* // 調(diào)用彈窗
* popTools.add('popId', {
* component: POP, // 彈窗組件
* position: { top: 200 } // 彈窗定位
* title: 'xxx', // 彈窗自定義props
* @click(e){ // 彈窗事件
* ....
* }
* })
*
*
* @example02 - 預(yù)注冊
* 通過預(yù)注冊組件,再次調(diào)用時,只需要傳入對應(yīng)注冊名稱,而不需要具體的配置項
* const [ pops, popTools ] = usePop()
*
* // 注冊本地彈窗
* popTools.componentsCache.add('userInfo', {
* component: CMP,
* opsition: { ... }
* ...
* })
*
* // 調(diào)用
* 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
}
}
}
/**
* 更新組件參數(shù)
* @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
}
}
}
/**
* 多處調(diào)用同一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 } ]
}
/**
* 嵌套結(jié)構(gòu)下的彈窗鉤子
*/
// 容器鉤子
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,
// 默認(rèn)定位
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)
}
}
}到此這篇關(guān)于vue usePop彈窗控制器的實現(xiàn)的文章就介紹到這了,更多相關(guān)vue usePop彈窗控制器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VUE中v-on:click事件中獲取當(dāng)前dom元素的代碼
這篇文章主要介紹了VUE中v-on:click事件中獲取當(dāng)前dom元素的代碼,文中同時給大家提到了v-on:click獲取當(dāng)前事件對象元素的方法,需要的朋友可以參考下2018-08-08
Pure admin-Router標(biāo)簽頁配置與頁面持久化實現(xiàn)方法詳解
這篇文章主要介紹了Pure admin-Router標(biāo)簽頁配置與頁面持久化實現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-01-01
解決VUEX刷新的時候出現(xiàn)數(shù)據(jù)消失
這篇文章主要介紹了解決VUEX刷新的時候出現(xiàn)數(shù)據(jù)消失,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07
Vue實現(xiàn)項目部署到非根目錄及解決刷新頁面時找不到資源
這篇文章主要介紹了Vue實現(xiàn)項目部署到非根目錄及解決刷新頁面時找不到資源問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03

