// 模态框模块

// 延迟获取 DOM 元素,避免模块加载时元素不存在
function getModalElements() {
    return {
        modalOverlay: document.getElementById('modalOverlay'),
        modalTitle: document.getElementById('modalTitle'),
        modalMessage: document.getElementById('modalMessage'),
        modalFooter: document.getElementById('modalFooter'),
        modalClose: document.getElementById('modalClose'),
        modalCancel: document.getElementById('modalCancel'),
        modalConfirm: document.getElementById('modalConfirm')
    };
}

let modalResolve = null;

export function showModal(options = {}) {
    return new Promise((resolve) => {
        const elements = getModalElements();
        if (!elements.modalOverlay || !elements.modalTitle || !elements.modalMessage || 
            !elements.modalFooter || !elements.modalConfirm || !elements.modalCancel) {
            console.error('模态框元素未找到');
            resolve(false);
            return;
        }
        
        modalResolve = resolve;
        const {
            title = '提示',
            message = '',
            type = 'alert',
            confirmText = '确定',
            cancelText = '取消'
        } = options;

        elements.modalTitle.textContent = title;
        elements.modalMessage.textContent = message;
        elements.modalConfirm.textContent = confirmText;
        elements.modalCancel.textContent = cancelText;

        if (type === 'alert') {
            elements.modalFooter.classList.add('single-button');
            elements.modalCancel.style.display = 'none';
        } else {
            elements.modalFooter.classList.remove('single-button');
            elements.modalCancel.style.display = 'block';
        }

        elements.modalOverlay.classList.add('show');
        setTimeout(() => elements.modalConfirm.focus(), 100);
    });
}

export function hideModal(result = false) {
    const elements = getModalElements();
    if (elements.modalOverlay) {
        elements.modalOverlay.classList.remove('show');
    }
    if (modalResolve) {
        modalResolve(result);
        modalResolve = null;
    }
}

export function customAlert(message, title = '提示') {
    return showModal({title, message, type: 'alert'});
}

export function customConfirm(message, title = '确认') {
    return showModal({title, message, type: 'confirm'});
}

// 初始化模态框事件
export function initModal() {
    const elements = getModalElements();
    if (!elements.modalOverlay) {
        console.warn('模态框元素未找到,无法初始化事件');
        return;
    }
    
    if (elements.modalClose) {
        elements.modalClose.addEventListener('click', () => hideModal(false));
    }
    if (elements.modalCancel) {
        elements.modalCancel.addEventListener('click', () => hideModal(false));
    }
    if (elements.modalConfirm) {
        elements.modalConfirm.addEventListener('click', () => hideModal(true));
    }
    elements.modalOverlay.addEventListener('click', (e) => {
        if (e.target === elements.modalOverlay) hideModal(false);
    });
    document.addEventListener('keydown', (e) => {
        if (e.key === 'Escape' && elements.modalOverlay.classList.contains('show')) {
            hideModal(false);
        }
    });
}

⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这段代码实现了一个简单的模态框(Modal)模块,负责显示/隐藏模态对话框并通过 Promise 返回用户的操作结果(确认/取消)。下面按模块和函数逐项说明其功能与行为,并给出几点注意/改进建议。

总体结构与目的

  • 延迟获取 DOM 元素(通过 getModalElements),避免模块加载时页面元素尚未存在导致 null。
  • showModal 返回一个 Promise,用户可以 await 或 .then 来获取模态框的结果(true 表示确认,false 表示取消/关闭)。
  • hideModal 隐藏模态框并解析(resolve)之前的 Promise。
  • 提供 customAlert 和 customConfirm 两个便捷函数分别用于单按钮提示和确认框。
  • initModal 用于在 DOM 中注册点击、遮罩和键盘(Esc)等事件处理器。

逐函数说明

  • getModalElements()

    • 每次调用时从 DOM 中读取并返回一组模态框相关的元素引用(overlay、title、message、footer、close、cancel、confirm)。
    • 这样做可以保证在元素尚未插入 DOM 时不会在模块加载阶段就报错。
  • showModal(options = {})

    • 返回一个新的 Promise,并把 resolve 保存到模块作用域变量 modalResolve(用于 later hideModal 调用)。
    • 获取 DOM 元素并做存在性校验;若缺少必要元素则记录错误并 resolve(false)。
    • options 支持:title、message、type('alert' 或 'confirm')、confirmText、cancelText。
    • 根据 type 调整 footer 的样式和取消按钮的显示('alert' 隐藏取消按钮并使用单按钮样式;'confirm' 显示两个按钮)。
    • 填充标题、消息和按钮文本。
    • 给 overlay 添加 class 'show' 以显示模态框(具体样式由 CSS 控制)。
    • 100ms 后把确认按钮 focus(提高键盘可达性)。
  • hideModal(result = false)

    • 再次获取元素并移除 overlay 的 'show' 类以隐藏模态框。
    • 如果存在之前保存的 modalResolve,就调用它并传入 result(通常 true 或 false),然后清空 modalResolve。
    • 这样调用者等待的 Promise 就会被 resolve,返回相应结果。
  • customAlert(message, title = '提示') / customConfirm(message, title = '确认')

    • 分别封装为调用 showModal 的快捷函数:alert 为单按钮、confirm 为双按钮模式。
  • initModal()

    • 获取元素并检查 overlay 是否存在;若不存在则打印警告并退出(说明未能初始化事件)。
    • 为关闭按钮、取消按钮、确认按钮分别绑定 click 事件,调用 hideModal(false/true)。
    • 为 overlay 绑定 click 事件:点击遮罩(并且事件目标正是 overlay)时关闭模态框(隐藏并 resolve(false))。
    • 为 document 绑定 keydown 事件:按 Esc 且 overlay 处于显示状态时关闭模态框。

交互与返回值

  • showModal 返回的 Promise 在用户点击确认按钮时 resolve(true),在点击取消、关闭按钮、点击遮罩或按 Esc 时 resolve(false)。
  • 若 showModal 在找不到 DOM 元素时被调用,会直接 resolve(false)。

注意与改进建议(可选)

  • 当前实现仅支持同时存在一个模态框(modalResolve 是单一变量),若需要支持并发多个模态框需改为队列或为每个模态创建独立 Promise/上下文。
  • getModalElements 每次都通过 document.getElementById 查询,性能上虽小但可用缓存或在初始化时绑定元素并在需要时检查存在性。
  • focus 管理比较简单:只把确认按钮 focus。可改进为记录并恢复之前的焦点,或在显示时把焦点限制在模态内(focus trap),以增强无障碍支持。
  • Esc 和 overlay 的事件监听在 initModal 中是全局绑定的,若页面存在多个模态或其他组件也监听 Esc,需要注意冲突。
  • CSS 控制显示/隐藏('show' 类)和单按钮样式需要在样式表中配套实现。

示例用法

  • 初始化(在 DOM 加载完后调用一次):
    • initModal();
  • 使用:
    • await customAlert('操作完成');
    • const ok = await customConfirm('确定要删除吗?'); // ok 为 true/false

如果你需要,我可以帮你:

  • 添加并发模态支持(队列/唯一 id);
  • 增强无障碍(focus trap、aria 属性);
  • 提供对应的 HTML/CSS 示例。
评论加载中...