You need to sign in or sign up before continuing.
GlobalPopupManager.js 4.54 KB
/**
 * 全局弹窗管理器
 *
 * @description 管理嵌套弹窗的层级和显示状态,解决弹窗遮挡问题
 * @module GlobalPopupManager
 * @author Claude Code
 * @version 2.0.0
 */

import { ref, computed } from 'vue'

/**
 * 全局状态:当前活动的弹窗列表
 * @type {Ref<string[]>}
 */
const activePopups = ref([])

/**
 * 是否有活动的子弹窗
 * @type {ComputedRef<boolean>}
 */
const hasActiveChildPopup = computed(() => activePopups.value.length > 0)

/**
 * 弹窗计数器(用于生成唯一 ID)
 * @type {number}
 */
let popupCounter = 0

/**
 * 父弹窗回调函数列表(全局共享)
 * @type {Function[]}
 */
const parentPopupCallbacks = []

/**
 * 注册父弹窗(用于 PlanPopupNew)
 *
 * @description 提供给父弹窗使用的 composable
 * @returns {Object} 父弹窗控制方法
 *
 * @example
 * const { registerFooterCallback, hasActiveChildPopup } = useParentPopup()
 *
 * // 注册回调,当子弹窗打开时自动隐藏父级 footer
 * const unregister = registerFooterCallback((shouldShowFooter) => {
 *   showFooter.value = shouldShowFooter
 * })
 */
export function useParentPopup() {
  /**
   * 注册底部按钮回调
   *
   * @param {Function} callback - 回调函数,接收 shouldShowFooter 参数
   * @returns {Function} 取消注册函数
   */
  const registerFooterCallback = (callback) => {
    parentPopupCallbacks.push(callback)

    // 返回取消注册函数
    return () => {
      const index = parentPopupCallbacks.indexOf(callback)
      if (index > -1) {
        parentPopupCallbacks.splice(index, 1)
      }
    }
  }

  /**
   * 通知所有回调
   *
   * @param {boolean} shouldShowFooter - 是否显示底部按钮
   */
  const notifyCallbacks = (shouldShowFooter) => {
    parentPopupCallbacks.forEach(callback => callback(shouldShowFooter))
  }

  return {
    registerFooterCallback,
    hasActiveChildPopup,
    notifyCallbacks
  }
}

/**
 * 注册全局弹窗(用于 DatePickerGlobal, SelectPickerGlobal 等)
 *
 * @description 提供给需要全局管理的弹窗组件使用
 * @returns {Object} 弹窗控制方法
 *
 * @example
 * const { registerPopup, activatePopup, deactivatePopup } = useGlobalPopup()
 *
 * // 组件挂载时注册
 * const popupId = ref(null)
 * onMounted(() => {
 *   popupId.value = registerPopup()
 * })
 *
 * // 弹窗打开时激活
 * activatePopup(popupId.value)
 *
 * // 弹窗关闭时停用
 * deactivatePopup(popupId.value)
 */
export function useGlobalPopup() {
  /**
   * 注册弹窗
   *
   * @description 生成唯一的弹窗 ID
   * @returns {string} 弹窗 ID(格式:popup-1, popup-2, ...)
   */
  const registerPopup = () => {
    popupCounter++
    return `popup-${popupCounter}`
  }

  /**
   * 激活弹窗
   *
   * @description 将弹窗添加到活动列表,触发父弹窗隐藏底部按钮
   * @param {string} popupId - 弹窗 ID
   */
  const activatePopup = (popupId) => {
    if (!activePopups.value.includes(popupId)) {
      activePopups.value.push(popupId)

      // 通知所有父弹窗隐藏底部按钮
      parentPopupCallbacks.forEach((callback) => {
        callback(false)
      })
    }
  }

  /**
   * 停用弹窗
   *
   * @description 从活动列表中移除弹窗,触发父弹窗显示底部按钮
   * @param {string} popupId - 弹窗 ID
   */
  const deactivatePopup = (popupId) => {
    const index = activePopups.value.indexOf(popupId)
    if (index > -1) {
      activePopups.value.splice(index, 1)

      // 如果没有其他活动弹窗了,通知所有父弹窗显示底部按钮
      if (activePopups.value.length === 0) {
        parentPopupCallbacks.forEach((callback) => {
          callback(true)
        })
      }
    }
  }

  return {
    registerPopup,
    activatePopup,
    deactivatePopup,
    hasActiveChildPopup
  }
}

/**
 * 注册子弹窗(旧接口,向后兼容)
 *
 * @description 提供给子弹窗使用的 composable
 * @param {Function} notifyParent - 通知父弹窗的函数
 * @returns {Object} 子弹窗控制方法
 */
export function useChildPopup(notifyParent) {
  /**
   * 子弹窗打开时通知父弹窗
   */
  const notifyParentOpen = () => {
    if (notifyParent) {
      notifyParent(false)
    }
  }

  /**
   * 子弹窗关闭时通知父弹窗
   */
  const notifyParentClose = () => {
    if (notifyParent) {
      notifyParent(true)
    }
  }

  return {
    notifyParentOpen,
    notifyParentClose
  }
}

/**
 * 重置所有弹窗状态
 *
 * @description 用于测试或异常情况下的状态重置
 */
export function resetPopupState() {
  activePopups.value = []
  popupCounter = 0
}