npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tker/popup

v1.0.6

Published

一个基于适配器模式的 Vue3 弹窗管理库,支持模态框、抽屉、消息提示和通知等多种弹窗类型。

Readme

@tker/popup

一个基于适配器模式的 Vue3 弹窗管理库,支持模态框、抽屉、消息提示和通知等多种弹窗类型。

特性

  • 🎯 适配器模式:支持多种 UI 框架适配(Naive UI、Element Plus 等)
  • 🔧 类型安全:完整的 TypeScript 支持
  • 🚀 高性能:组件缓存机制,避免重复渲染
  • 📦 轻量级:模块化设计,按需加载
  • 🎨 灵活配置:支持全局和实例级配置
  • 🔄 生命周期管理:完整的弹窗生命周期控制

项目结构

src/
├── components/                 # 组件目录
│   ├── PopupProvider.vue      # 弹窗渲染提供者
│   └── adapter/               # 适配器组件
│       └── naive-ui/          # Naive UI 适配器
│           ├── modal/         # 模态框适配器
│           ├── drawer/        # 抽屉适配器
│           ├── message/       # 消息适配器
│           └── notification/  # 通知适配器
├── composables/               # 组合式 API
│   └── usePopup.ts           # 核心 API 入口
├── core/                      # 核心管理器
│   ├── PopupManager.ts       # 弹窗管理器
│   ├── AdaptManager.ts       # 适配器管理器
│   ├── InstanceManager.ts    # 实例管理器
│   └── Adapter.ts            # 适配器基类
├── typing/                    # 类型定义
│   ├── popup.ts              # 弹窗类型
│   ├── adapter.ts            # 适配器类型
│   └── index.ts              # 类型导出
├── utils/                     # 工具函数
│   └── helpers.ts            # 辅助函数
└── index.ts                   # 主入口文件

安装

pnpm add @tker/popup

推荐的项目组织方式

为了更好地组织 popup 相关代码,建议采用以下目录结构:

src/
├── shared/
│   └── popup/
│       └── index.ts          # 导出 setupPopup 函数
├── App.vue                   # 在根组件中使用 PopupProvider
├── main.ts                   # 初始化时调用 setupPopup
└── router/
│   └── index.ts              # 路由配置

1. 创建 popup 配置目录

将 popup 相关的适配器配置集中管理:

src/shared/popup/index.ts - 初始化配置

import {
    getPopupManager,
    NDrawerAdapter,
    NMessageAdapter,
    NModalAdapter,
    NNotificationAdapter,
} from "@tker/popup";

import { NMessageProvider, NNotificationProvider } from "naive-ui";

/**
 * 初始化 Popup 配置
 */
export function setupPopup(): void {
    const manager = getPopupManager();

    // 配置模态框适配器
    manager.setModalAdapter({
        render: NModalAdapter,
    });

    // 配置消息适配器(需要 provider)
    manager.setMessageAdapter({
        render: NMessageAdapter,
        provider: NMessageProvider,
    });

    // 配置通知适配器(需要 provider)
    manager.setNotificationAdapter({
        render: NNotificationAdapter,
        provider: NNotificationProvider,
    });

    // 配置抽屉适配器
    manager.setDrawerAdapter({
        render: NDrawerAdapter,
    });
}

说明

  • NModalAdapterNDrawerAdapter:内置的 NaiveUI 模态框和抽屉适配器
  • NMessageAdapterNNotificationAdapter:内置的 NaiveUI 消息和通知适配器
  • NMessageProviderNNotificationProvider:NaiveUI 的 Provider 组件,用于提供上下文

2. 在 App.vue 中使用 PopupProvider

<script setup lang="ts">
import { PopupProvider } from "@tker/popup";
</script>

<template>
    <popup-provider />
    <router-view />
</template>

3. 在 main.ts 中初始化

import { createApp } from "vue";

import App from "./App.vue";
import { setupPopup } from "./shared/popup";
import { setupRouter } from "./router";

const app = createApp(App);
setupRouter(app);
setupPopup();

app.mount("#app");

这种组织方式的优点

  1. 职责分离:popup 配置独立于业务组件
  2. 易于维护:适配器配置集中管理
  3. 初始化顺序清晰main.ts 中统一调用 setupPopup()
  4. 便于切换 UI 库:只需修改 setupPopup 中的适配器配置

内置 NaiveUI 适配器

框架提供以下内置适配器,可从 @tker/popup 直接导入:

import {
    NModalAdapter,
    NDrawerAdapter,
    NMessageAdapter,
    NNotificationAdapter,
    Confirm,
    Prompt,
} from "@tker/popup";

| 适配器 | 说明 | 需要 Provider | |--------|------|---------------| | NModalAdapter | 模态框适配器 | 否 | | NDrawerAdapter | 抽屉适配器 | 否 | | NMessageAdapter | 消息适配器 | 需要 NMessageProvider | | NNotificationAdapter | 通知适配器 | 需要 NNotificationProvider | | Confirm | 确认对话框组件 | 通过 NModalAdapter 渲染 | | Prompt | 输入对话框组件 | 通过 NModalAdapter 渲染 |

注意:Message 和 Notification 需要配置 Provider 才能正常工作:

manager.setMessageAdapter({
    render: NMessageAdapter,
    provider: NMessageProvider,  // 必须提供
});

manager.setNotificationAdapter({
    render: NNotificationAdapter,
    provider: NNotificationProvider,  // 必须提供
});

快速开始

1. 配置适配器(旧方式,推荐使用 setupPopup)

如果不想使用 setupPopup 函数,也可以直接在 main.ts 中配置:

import { createApp } from "vue";
import {
    PopupProvider,
    getPopupManager,
    NModalAdapter,
    NDrawerAdapter,
    NMessageAdapter,
    NNotificationAdapter,
} from "@tker/popup";

import { NMessageProvider, NNotificationProvider } from "naive-ui";

const app = createApp(App);

// 获取弹窗管理器并配置适配器
const popupManager = getPopupManager();

// 配置各类型适配器
popupManager.setModalAdapter({ render: NModalAdapter });
popupManager.setDrawerAdapter({ render: NDrawerAdapter });
popupManager.setMessageAdapter({
    render: NMessageAdapter,
    provider: NMessageProvider,
});
popupManager.setNotificationAdapter({
    render: NNotificationAdapter,
    provider: NNotificationProvider,
});

app.mount("#app");

2. 在模板中添加 PopupProvider

<template>
  <div id="app">
    <!-- 你的应用内容 -->
    <router-view />
    
    <!-- 弹窗提供者 -->
    <PopupProvider />
  </div>
</template>

3. 使用弹窗 API

<script setup>
import { 
  modal, 
  drawer, 
  message, 
  notification,
  alert,
  confirm,
  prompt
} from '@tker/popup'

// 模态框
const openModal = async () => {
  const result = modal({
    content: '这是一个模态框',
    popupOptions: {
      title: '标题'
    }
  })
  
  // 通过 promise 属性获取用户操作结果
  // userAction 的值取决于 content 的类型:
  // - 当 content 为字符串时:positive 返回 true,negative 返回 false
  // - 当 content 为 Confirm 组件时:positive 返回 true,negative 返回 false
  // - 当 content 为 Prompt 组件时:positive 返回输入的值,negative 返回 false
  // - 当 content 为自定义组件时:返回值由组件内部的 setPositive/setNegative 函数决定
  const userAction = await result.promise
  
  if (userAction) {
    console.log('用户确认了操作,返回值:', userAction)
  } else {
    console.log('用户取消了操作')
  }
}

// 抽屉
const openDrawer = () => {
  drawer({
    content: '抽屉内容',
    popupOptions: {
      title: '抽屉标题',
      placement: 'right'
    }
  })
}

// 消息提示
const showMessage = () => {
  message('操作成功', 'success', 3000)
}

// 通知
const showNotification = () => {
  notification('通知内容', '通知标题', 'info', 5000)
}

// 警告框
const showAlert = () => {
  alert('这是一个警告信息', '警告')
}

// 确认框
const showConfirm = async () => {
  const confirmResult = confirm('确定要删除吗?', '确认删除')
  // confirm 的 userChoice 值:positive 返回 true,negative 返回 false
  const userChoice = await confirmResult.promise
  if (userChoice) {
    console.log('用户确认删除')
  }
}

// 输入框
const showPrompt = async () => {
  const promptResult = prompt('请输入您的姓名', '默认值')
  // prompt 的 userInput 值:positive 返回用户输入的字符串,negative 返回 false
  const userInput = await promptResult.promise
  if (userInput) {
    console.log('用户输入:', userInput)
  }
}
</script>

API 参考

基础 API

modal(config)

创建模态框,返回PopupResult对象

modal({
  content: Component | string | VNode,
  props?: Record<string, any>,
  popupOptions?: {
    title?: string,
    width?: string | number,
    closable?: boolean,
    maskClosable?: boolean
  }
}): PopupResult<'modal'>

PopupResult 接口

interface PopupResult<T> {
  id: string;           // 弹窗实例ID
  promise: Promise<any>; // 用户操作结果的Promise
  close: () => void;    // 关闭弹窗的方法
  update: (config: Partial<PopupOptionsMap[T]>) => void; // 更新弹窗配置的方法
}

userAction 返回值说明

result.promise 解析后的 userAction 值取决于 content 参数的类型:

| content 类型 | positive 按钮返回值 | negative 按钮返回值 | 说明 | |-------------|-------------------|-------------------|------| | 字符串 | true | false | 默认的确认/取消行为 | | Confirm 组件 | true | false | 确认对话框的标准返回值 | | Prompt 组件 | 用户输入的值 | false | 输入对话框返回用户输入内容 | | 自定义组件 | 由 setPositive() 决定 | 由 setNegative() 决定 | 组件内部可自定义返回值 |

示例:

// 字符串内容
const result1 = modal({ content: '确认删除?' })
const action1 = await result1.promise // true 或 false

// Prompt 组件
const result2 = modal({ content: Prompt, props: { placeholder: '请输入名称' } })
const action2 = await result2.promise // 用户输入的字符串 或 false

// 自定义组件
const result3 = modal({ content: MyCustomComponent })
const action3 = await result3.promise // 由组件内 setPositive/setNegative 决定

drawer(config)

创建抽屉,返回PopupResult对象

drawer({
  content: Component | string | VNode,
  props?: Record<string, any>,
  popupOptions?: {
    title?: string,
    placement?: 'left' | 'right' | 'top' | 'bottom',
    width?: string | number,
    height?: string | number,
    closable?: boolean,
    maskClosable?: boolean
  }
}): PopupResult<'drawer'>

message(configOrContent, type?, duration?)

显示消息提示

参数:

  • configOrContent: 消息内容(字符串)或完整配置对象(MessageProps)
  • type: 消息类型('error' | 'info' | 'success' | 'warning')
  • duration: 显示时长(毫秒)

返回: PopupResult<'message'>

多种传参方式:

// 方式1:字符串 + 可选参数
message('操作成功', 'success', 3000)

// 方式2:配置对象
message({
  content: '操作成功',
  type: 'success',
  duration: 3000,
  position: 'top',
  showIcon: true
})

类型定义:

message(
  configOrContent: MessageProps | string,
  type?: 'success' | 'error' | 'warning' | 'info',
  duration?: number
): PopupResult<'message'>

interface MessageProps {
  duration?: number;
  type?: 'error' | 'info' | 'success' | 'warning';
  showIcon?: boolean;
  position?: PopupPositionValue;
  content?: string;
}

notification(configOrContent?, title?, type?, duration?)

显示通知

参数:

  • configOrContent: 通知内容(字符串)或完整配置对象(NotificationProps)
  • title: 通知标题
  • type: 通知类型('error' | 'info' | 'success' | 'warning')
  • duration: 显示时长(毫秒)

返回: PopupResult<'notification'>

多种传参方式:

// 方式1:字符串 + 可选参数
notification('系统更新完成', '系统通知', 'success', 5000)

// 方式2:配置对象
notification({
  content: '系统更新完成',
  title: '系统通知',
  type: 'success',
  duration: 5000,
  position: 'top-right'
})

类型定义:

notification(
  configOrContent?: NotificationProps | string,
  title?: string,
  type?: 'success' | 'error' | 'warning' | 'info',
  duration?: number
): PopupResult<'notification'>

interface NotificationProps {
  title?: string;
  duration?: number;
  position?: NotificationPosition;
  type?: 'error' | 'info' | 'success' | 'warning';
  content?: string;
}

便捷方法

消息便捷方法

success(content: string, duration?: number)
error(content: string, duration?: number)
warning(content: string, duration?: number)
info(content: string, duration?: number)

通知便捷方法

successNotification(content: string, title?: string, duration?: number)
errorNotification(content: string, title?: string, duration?: number)
warningNotification(content: string, title?: string, duration?: number)
infoNotification(content: string, title?: string, duration?: number)

对话框方法

// 警告框
alert(
  configOrContent: Omit<AlertProps, "title"> | string,
  title?: string
): PopupResult<'modal'>

**参数:**
- `configOrContent`: 警告内容(字符串)或完整配置对象(AlertProps)
- `title`: 警告标题(可选)

**返回:** `PopupResult<'modal'>`

**多种传参方式:**

```typescript
// 方式1:字符串 + 可选标题
alert('操作失败,请重试', '错误提示')

// 方式2:配置对象
alert({
  content: '操作失败,请重试',
  positiveText: '我知道了'
}, '错误提示')

类型定义:

interface AlertProps {
  positiveText?: string;
  content?: string;
}

// 确认框 confirm( configOrContent: Omit<ConfirmProps, "title"> | string, title?: string ): PopupResult<'modal'>

参数:

  • configOrContent: 确认内容(字符串)或完整配置对象(ConfirmProps)
  • title: 确认标题(可选)

返回: PopupResult<'modal'>

多种传参方式:

// 方式1:字符串 + 可选标题
confirm('确定要删除这条记录吗?', '删除确认')

// 方式2:配置对象
confirm({
  content: '确定要删除这条记录吗?',
  positiveText: '确定删除',
  negativeText: '取消'
}, '删除确认')

类型定义:

interface ConfirmProps {
  title?: string;
  negativeText?: string;
  positiveText?: string;
  content?: string;
}

// 输入框 prompt( configOrTitle: PromptProps | string, defaultValue?: string ): PopupResult<'modal'>

参数:

  • configOrTitle: 输入框标题(字符串)或完整配置对象(PromptProps)
  • defaultValue: 默认输入值(可选)

返回: PopupResult<'modal'>

多种传参方式:

// 方式1:字符串标题 + 默认值
prompt('请输入您的姓名', '张三')

// 方式2:配置对象
prompt({
  title: '请输入您的姓名',
  defaultValue: '张三',
  placeholder: '请输入姓名',
  type: 'text',
  maxlength: 20,
  validate: (value) => {
    if (!value.trim()) return '姓名不能为空'
    if (value.length < 2) return '姓名至少2个字符'
    return undefined
  }
})

类型定义:

interface PromptProps {
  title?: string;
  type?: 'password' | 'text' | 'textarea';
  negativeText?: string;
  positiveText?: string;
  defaultValue?: string;
  placeholder?: string;
  maxlength?: number;
  size?: 'large' | 'medium' | 'small' | 'tiny';
  validate?: (value: string) => string | undefined;
}

管理方法

// 关闭所有弹窗
closeAll(type?: PopupType)

// 创建关闭事件发射器
createCloseEmitter(): () => boolean

// 创建销毁事件发射器
createDestroyEmitter(): () => boolean

// 创建离开后事件发射器
createAfterLeaveEmitter(): () => void

自定义适配器

创建自定义适配器

本库支持创建自定义适配器来扩展弹窗功能,适配不同的UI框架或实现特定的弹窗效果。

1. 基础适配器结构

import { getPopupManager } from '@tker/popup'
import { defineComponent } from 'vue'

// 创建适配器组件
const MyCustomAdapter = defineComponent({
  name: 'MyCustomAdapter',
  props: {
    content: String,
    title: String,
    // 其他自定义属性
  },
  setup(props) {
    // 适配器逻辑实现
    return () => {
      // 渲染逻辑
    };
  }
});

// 注册适配器
const popupManager = getPopupManager()
popupManager.setModalAdapter({ 
  render: MyCustomAdapter,
  maxInstance: 5, // 可选:最大实例数
  closeAnimationEventName: 'afterLeave' // 可选:关闭动画事件名
})

2. 适配器类型定义

// 适配器选项接口
interface AdapterOptions<T extends keyof PopupOptionsMap> {
  render: AdapterRender; // 渲染函数或组件
  maxInstance?: number; // 最大实例数
  closeAnimationEventName?: string; // 关闭动画事件名
  provider?: AdapterProvider | Component; // 提供者组件
}

// 渲染类型
type AdapterRender = 
  | Component // Vue组件
  | VNode // 虚拟节点
  | (() => Component | Promise<Component> | VNode | Promise<VNode>); // 渲染函数

3. 实际示例:Element Plus 适配器

// ElementMessageAdapter.vue
<template>
  <div ref="messageRef"></div>
</template>

<script setup lang="ts">
import { ElMessage } from 'element-plus';
import { onMounted } from 'vue';
import { createAfterLeaveEmitter } from '@tker/popup';

interface MessageProps {
  content: string;
  type?: 'success' | 'warning' | 'info' | 'error';
  duration?: number;
  showClose?: boolean;
}

const props = withDefaults(defineProps<MessageProps>(), {
  type: 'info',
  duration: 3000,
  showClose: false
});

onMounted(() => {
  const instance = ElMessage({
    message: props.content,
    type: props.type,
    duration: props.duration,
    showClose: props.showClose,
    onClose: createAfterLeaveEmitter() // 重要:通知弹窗系统关闭
  });
});
</script>
// 注册 Element Plus 适配器
import { getPopupManager } from '@tker/popup'
import ElementMessageAdapter from './ElementMessageAdapter.vue'

const popupManager = getPopupManager()
popupManager.setMessageAdapter({ 
  render: ElementMessageAdapter,
  maxInstance: 10
})

4. 适配器最佳实践

  1. 生命周期管理:确保在适配器组件中正确处理关闭事件
import { 
  createAfterLeaveEmitter,
  createPositiveEmitter,
  createNegativeEmitter,
  createCloseEmitter 
} from '@tker/popup';

// 关闭动画完成后的回调
const onAfterLeave = createAfterLeaveEmitter();

// 直接关闭弹窗
const close = createCloseEmitter();

// 确认按钮处理器
const positiveEmitter = createPositiveEmitter();
const handlePositiveClick = async () => {
  const result = await positiveEmitter(); // 执行用户定义的确认逻辑
  if (result !== undefined) {
    close(); // 只有当返回值不为undefined时才关闭弹窗
  }
};

// 取消按钮处理器
const negativeEmitter = createNegativeEmitter();
const handleNegativeClick = async () => {
  const result = await negativeEmitter(); // 执行用户定义的取消逻辑
  if (result !== undefined) {
    close(); // 只有当返回值不为undefined时才关闭弹窗
  }
};
  1. 类型安全:为适配器定义明确的Props接口
interface CustomAdapterProps {
  content: string;
  title?: string;
  // 其他属性
}
  1. 错误处理:适配器应该处理可能的错误情况
const MyAdapter = defineComponent({
  setup(props) {
    try {
      // 适配器逻辑
    } catch (error) {
      console.error('适配器错误:', error);
    }
  }
});
  1. 性能优化:合理设置maxInstance避免过多实例
popupManager.setMessageAdapter({ 
  render: MessageAdapter,
  maxInstance: 5 // 限制最大实例数
})

适配器管理方法

弹窗管理器提供了便捷的适配器设置方法:

import { getPopupManager } from '@tker/popup'

const popupManager = getPopupManager()

// 通用适配器注册方法
popupManager.addAdapter({
  type: 'modal', // 弹窗类型
  render: MyModalAdapter, // 渲染组件
  maxInstance: 5, // 最大实例数
})

// 便捷的特定类型适配器设置方法
popupManager.setModalAdapter({ render: MyModalAdapter })
popupManager.setDrawerAdapter({ render: MyDrawerAdapter })
popupManager.setMessageAdapter({ render: MyMessageAdapter })
popupManager.setNotificationAdapter({ render: MyNotificationAdapter })

完整配置示例

import { getPopupManager } from '@tker/popup'
import MyModalAdapter from './adapters/MyModalAdapter.vue'
import MyDrawerAdapter from './adapters/MyDrawerAdapter.vue'
import MyMessageAdapter from './adapters/MyMessageAdapter.vue'
import MyNotificationAdapter from './adapters/MyNotificationAdapter.vue'
import MyAlertAdapter from './adapters/MyAlertAdapter.vue'
import MyConfirmAdapter from './adapters/MyConfirmAdapter.vue'
import MyPromptAdapter from './adapters/MyPromptAdapter.vue'

const popupManager = getPopupManager()

// 配置所有类型的适配器
popupManager.setModalAdapter({ render: MyModalAdapter, maxInstance: 3 })
popupManager.setDrawerAdapter({ render: MyDrawerAdapter, maxInstance: 2 })
popupManager.setMessageAdapter({ render: MyMessageAdapter, maxInstance: 10 })
popupManager.setNotificationAdapter({ render: MyNotificationAdapter, maxInstance: 8 })

配置选项

全局配置

import { createPopupManager } from '@tker/popup'

// 创建带配置的弹窗管理器
const popupManager = createPopupManager({
  defaultZIndex: 1000,
  enableMemoryOptimization: true
})

// 或者使用默认管理器
import { getPopupManager } from '@tker/popup'
const manager = getPopupManager()

TypeScript 支持

库提供完整的 TypeScript 类型定义:

import type {
  PopupInstance,
  PopupResult,
  ModalConfig,
  DrawerConfig,
  MessageConfig,
  NotificationConfig
} from '@tker/popup'

Emitter 机制详解

核心概念

createPositiveEmittercreateNegativeEmitter 是弹窗系统中的核心机制,用于处理用户的确认和取消操作。它们的设计目的是让开发者能够在弹窗中执行异步操作,并根据操作结果决定是否关闭弹窗。

confirm vs modal 的选择

使用 confirm

  • 简单的确认/取消场景
  • 返回值固定为 true(确认)或 false(取消)
  • 内置标准的确认对话框样式
  • 适用于删除确认、操作确认等标准场景

使用 modal + 自定义组件:

  • 需要复杂交互的场景
  • 需要自定义返回值的场景
  • 需要在确认前执行异步验证或操作
  • 需要自定义UI和交互逻辑
// ✅ 推荐:简单确认使用confirm
const deleteConfirm = await confirm('确定删除吗?', '删除确认');
if (await deleteConfirm.promise) {
  // 用户确认删除
}

// ✅ 推荐:复杂场景使用modal
const formResult = await modal({
  content: FormComponent,
  props: { initialData }
});
const formData = await formResult.promise; // 可以是任何自定义数据

工作原理

// 1. 简单确认场景 - 使用confirm
const result = await confirm(
  '确定要删除这个文件吗?',
  '删除确认'
);
const userChoice = await result.promise; // true或false

// 2. 复杂场景 - 使用modal + 自定义组件
const complexResult = await modal({
  title: '高级操作',
  content: CustomComponent,
  props: { data: someData }
});

// 在CustomComponent中使用setPositive/setNegative
// 可以执行复杂的异步逻辑并返回自定义结果
// 2. 在适配器中使用emitter
const positiveEmitter = createPositiveEmitter();
const negativeEmitter = createNegativeEmitter();

const handlePositiveClick = async () => {
  loading.value = true;
  try {
    const result = await positiveEmitter(); // 调用用户定义的positive函数
    if (result !== undefined) {
      close(); // 只有返回值不为undefined时才关闭弹窗
    }
  } finally {
    loading.value = false;
  }
};

返回值处理规则

  • 返回 undefined:弹窗保持打开状态,通常用于验证失败或操作失败的情况
  • 返回任何其他值:弹窗将关闭,返回值会作为 Promise 的 resolve 值
  • 抛出异常:弹窗保持打开状态,异常需要在适配器中处理

使用场景

  1. 表单验证:在确认前验证表单数据
  2. 异步操作:执行网络请求、文件操作等
  3. 用户确认:二次确认重要操作
  4. 条件关闭:根据业务逻辑决定是否关闭弹窗

完整适配器示例

以下是一个完整的模态框适配器示例,展示了如何正确使用 emitter 机制:

<template>
  <div class="modal-overlay" v-if="visible">
    <div class="modal-content">
      <div class="modal-header">
        <h3>{{ title }}</h3>
      </div>
      <div class="modal-body">
        <slot>{{ content }}</slot>
      </div>
      <div class="modal-footer">
        <button 
          v-if="negativeText"
          @click="handleNegativeClick"
          :disabled="loading"
          class="btn btn-cancel"
        >
          {{ negativeText }}
        </button>
        <button 
          v-if="positiveText"
          @click="handlePositiveClick"
          :disabled="loading"
          class="btn btn-confirm"
        >
          <span v-if="loading">处理中...</span>
          <span v-else>{{ positiveText }}</span>
        </button>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { 
  createPositiveEmitter,
  createNegativeEmitter,
  createCloseEmitter,
  createAfterLeaveEmitter 
} from '@tker/popup';

interface Props {
  visible?: boolean;
  title?: string;
  content?: string;
  positiveText?: string;
  negativeText?: string;
}

withDefaults(defineProps<Props>(), {
  visible: false,
  positiveText: '确定',
  negativeText: '取消'
});

const loading = ref(false);

// 创建emitter
const positiveEmitter = createPositiveEmitter();
const negativeEmitter = createNegativeEmitter();
const close = createCloseEmitter();
const onAfterLeave = createAfterLeaveEmitter();

// 确认按钮点击处理
const handlePositiveClick = async () => {
  loading.value = true;
  try {
    const result = await positiveEmitter();
    // 只有当返回值不为undefined时才关闭弹窗
    if (result !== undefined) {
      close();
    }
  } catch (error) {
    console.error('确认操作失败:', error);
    // 发生错误时不关闭弹窗,让用户重试
  } finally {
    loading.value = false;
  }
};

// 取消按钮点击处理
const handleNegativeClick = async () => {
  try {
    const result = await negativeEmitter();
    // 只有当返回值不为undefined时才关闭弹窗
    if (result !== undefined) {
      close();
    }
  } catch (error) {
    console.error('取消操作失败:', error);
    // 即使取消操作失败,通常也应该关闭弹窗
    close();
  }
};
</script>

使用示例

// 表单验证场景
const result = await modal({
  title: '保存表单',
  content: '确定要保存当前表单吗?',
  positive: async () => {
    // 验证表单
    const isValid = validateForm();
    if (!isValid) {
      showMessage('表单验证失败,请检查输入');
      return; // 返回undefined,弹窗保持打开
    }
    
    // 提交表单
    try {
      await submitForm();
      showMessage('保存成功');
      return true; // 返回值,弹窗关闭
    } catch (error) {
      showMessage('保存失败,请重试');
      return; // 返回undefined,弹窗保持打开
    }
  },
  negative: () => {
    return false; // 直接关闭弹窗
  }
});

// 删除确认场景 - 使用confirm
const deleteResult = await confirm(
  `确定要删除 "${fileName}" 吗?此操作不可撤销。`,
  '删除确认'
);

// confirm返回boolean值:true表示确认,false表示取消
if (deleteResult.promise) {
  const userConfirmed = await deleteResult.promise;
  if (userConfirmed) {
    try {
      await deleteFile(fileId);
      showMessage('文件删除成功');
    } catch (error) {
      showMessage('删除失败:' + error.message);
    }
  }
}

// 复杂异步操作场景 - 使用modal
const complexResult = await modal({
  title: '批量处理',
  content: BatchProcessComponent,
  props: { fileList },
  popupOptions: {
    positive: true,
    negative: true
  }
});

// 在BatchProcessComponent中使用setPositive/setNegative
// 可以返回复杂的处理结果

最佳实践

  1. 适配器配置:在应用启动时配置所需的适配器组件
  2. 内存管理:使用 closeAll() 在路由切换时清理弹窗
  3. 错误处理:为异步操作添加适当的错误处理
  4. 类型安全:充分利用 TypeScript 类型定义
  5. 性能优化:利用组件缓存机制,避免频繁创建销毁
  6. 管理器使用:推荐使用 getPopupManager() 获取全局管理器实例
  7. Emitter使用:正确处理 emitter 的返回值,提供良好的用户体验

许可证

MIT License