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

@gopowerteam/modal-render

v1.0.1

Published

Vue 3 模态框/抽屉渲染组件库,基于 Promise 风格 API,提供灵活的对话框和抽屉功能。

Downloads

355

Readme

@gopowerteam/modal-render

Vue 3 模态框/抽屉渲染组件库,基于 Promise 风格 API,提供灵活的对话框和抽屉功能。

目录

特性

  • 支持 Dialog(对话框)和 Drawer(抽屉)两种模式
  • Promise 风格 API,支持 async/await
  • 预设对话框:confirm、info、warning、error、success
  • 可拖拽、全屏、自定义尺寸
  • 支持嵌套模态框
  • 完整的 TypeScript 类型支持

安装

pnpm add @gopowerteam/modal-render

快速开始

1. 配置 ModalProvider

App.vue 中配置 ModalProvider

<script setup lang="ts">
import { ModalProvider } from '@gopowerteam/modal-render'
</script>

<template>
  <ModalProvider :offset="{ x: 0, y: 0 }">
    <RouterView />
  </ModalProvider>
</template>

2. 导入样式

在入口文件 main.ts 中导入样式:

import '@gopowerteam/modal-render/dist/style.css'

3. 基础使用

<script setup lang="ts">
import { useModal } from '@gopowerteam/modal-render'
import MyModalContent from './components/MyModalContent.vue'

const modal = useModal()

function openModal() {
  modal.open(
    MyModalContent,
    { /* props */ },
    {
      title: '标题',
      footer: true,
      onOk({ close }) {
        // 处理确认
        close()
      },
    },
  )
}
</script>

<template>
  <button @click="openModal">打开弹窗</button>
</template>

快速参考

导入

// 组件
import { ModalProvider, ModalHeader, ModalFooter } from '@gopowerteam/modal-render'

// Hook
import { useModal, onSubmit } from '@gopowerteam/modal-render'

// 类型
import type { OpenModalOptions, SizeOptions, ShowLoadingOptions } from '@gopowerteam/modal-render'

// 样式
import '@gopowerteam/modal-render/dist/style.css'

useModal 方法速查

const modal = useModal()

// 打开组件
modal.open(Component, props?, options?) → Promise<any> & { close }

// 预设对话框
modal.confirm({ title, content, onOk?, onCancel? })
modal.info({ title, content })
modal.success({ title, content })
modal.warning({ title, content })
modal.error({ title, content })

// 关闭
modal.close(data?)      // 关闭当前模态框
modal.closeAll()        // 关闭所有模态框

// 加载状态
modal.showLoading(options?) → () => void  // 返回关闭函数
modal.hideLoading()

常用 options 配置

// 基础对话框
{ title: '标题', footer: true }

// 抽屉
{ mode: 'drawer', position: 'right', title: '标题' }

// 全屏
{ fullscreen: true }

// 可拖拽
{ draggable: true }

// 点击遮罩关闭
{ maskClosable: true }

// 自定义尺寸
{ width: 800, height: 600 }
{ size: 'large' }  // small | middle | large

// 隐藏头部
{ header: false }

// 表单提交
{ footer: true, form: 'formName', onOk: ({ close }) => {} }

onSubmit 用法

// 在模态框内容组件中
onSubmit((actions) => {
  actions.showLoading()
  // 异步操作完成后
  actions.close(data)
})

ModalProvider 配置

<ModalProvider
  :append-to-body="false"
  :sizes="{ small: '50%', middle: '70%', large: '90%' }"
  max-width="90%"
  max-height="90%"
  :offset="{ x: 0, y: 0 }"
>
  <App />
</ModalProvider>

组件 API

ModalProvider

最外层容器组件,用于提供模态框上下文。

| 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | appendToBody | boolean \| string | false | 是否挂载到 body,传入字符串时作为选择器 | | sizes | SizeOptions | { small: '50%', middle: '70%', large: '90%' } | 预设尺寸配置 | | maxWidth | string \| number | '90%' | 最大宽度 | | maxHeight | string \| number | '90%' | 最大高度 | | offset | { x?: number, y?: number } | { x: 0, y: 0 } | 偏移量 |

<ModalProvider
  :append-to-body="true"
  :sizes="{ small: '400px', middle: '600px', large: '80%' }"
  max-width="1200px"
  :offset="{ x: 100, y: 50 }"
>
  <App />
</ModalProvider>

ModalHeader

自定义模态框头部,插入到默认头部位置。

<script setup lang="ts">
import { ModalHeader } from '@gopowerteam/modal-render'
</script>

<template>
  <ModalHeader>
    <span>自定义标题</span>
  </ModalHeader>
</template>

ModalFooter

自定义模态框底部,插入到默认底部位置。

<script setup lang="ts">
import { ModalFooter, useModal } from '@gopowerteam/modal-render'

const modal = useModal()
</script>

<template>
  <ModalFooter>
    <button @click="modal.close()">关闭</button>
    <button @click="modal.close({ saved: true })">保存</button>
  </ModalFooter>
</template>

useModal Hook

获取模态框操作方法,必须在 ModalProvider 内部使用。

const modal = useModal()

open()

打开自定义组件或预设对话框。

// 打开自定义组件
modal.open(component: Component, props?: Record<string, any>, options?: OpenModalOptions): Promise<any> & { close: () => void }

// 打开预设对话框
modal.open('confirm' | 'info' | 'warning' | 'error' | 'success', props: object, options?: OpenModalOptions): Promise<any> & { close: () => void }

返回值:返回 Promise 和 close 方法,Promise resolve 的值即为 close(data) 传入的数据。

<script setup lang="ts">
const modal = useModal()

async function openAndWait() {
  const result = await modal.open(
    MyComponent,
    { message: 'Hello' },
    { title: '标题' }
  )
  console.log('模态框返回:', result)
}

// 或者使用 close 方法
const instance = modal.open(MyComponent, {}, { title: '标题' })
instance.then(result => console.log(result))
instance.close() // 主动关闭
</script>

close()

关闭当前所在模态框。

modal.close() // 不传值,相当于取消
modal.close(data) // 传递数据,Promise 会 resolve 此数据

closeAll()

关闭所有打开的模态框。

modal.closeAll()

预设对话框

快捷方法打开预设对话框。

// 确认对话框
modal.confirm({
  title: '提示',
  content: '确定要执行此操作吗?',
  async onOk() {
    await doSomething()
  },
  onCancel() {
    console.log('取消了')
  },
})

// 信息对话框
modal.info({
  title: '提示',
  content: '操作成功',
})

// 成功/警告/错误对话框用法相同
modal.success({ title: '完成', content: '保存成功' })
modal.warning({ title: '警告', content: '请检查输入' })
modal.error({ title: '错误', content: '操作失败' })

showLoading() / hideLoading()

显示/隐藏加载状态。

// 在模态框内容组件内使用
const { showLoading, hideLoading } = modal

function handleAsync() {
  const closeLoading = showLoading({ text: '处理中...' })
  
  fetchData().then(() => {
    closeLoading()
  })
}

// 全局 Loading(无需打开模态框)
function showGlobalLoading() {
  const close = modal.showLoading({
    text: '加载中...',
  })
  
  // 3秒后自动关闭
  setTimeout(() => {
    close()
  }, 3000)
}

OpenModalOptions 配置项

| 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | mode | 'dialog' \| 'drawer' | 'dialog' | 显示模式 | | position | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | 抽屉方向(drawer 模式) | | size | 'small' \| 'middle' \| 'large' | 'middle' | 预设尺寸 | | width | string \| number | - | 自定义宽度 | | height | string \| number | - | 自定义高度 | | fullscreen | boolean | false | 全屏显示 | | draggable | boolean | false | 可拖拽移动 | | title | string | - | 标题文字 | | header | boolean | true | 是否显示头部 | | footer | boolean | false | 是否显示底部按钮 | | closeable | boolean | true | 是否显示关闭按钮 | | esc | boolean | false | 按 ESC 键关闭 | | mask | boolean | true | 是否显示遮罩 | | maskClosable | boolean | false | 点击遮罩关闭 | | bodyStyle | CSSProperties | - | 内容区样式 | | backgroundColor | string | - | 背景色 | | submitText | string | '确定' | 确认按钮文字 | | cancelText | string | '取消' | 取消按钮文字 | | form | string | - | 关联表单的 name,配合 onSubmit 使用 | | onOk | (options: { close: () => void }) => void | - | 确认回调 | | onCancel | () => void | - | 取消回调 |


onSubmit Hook

在模态框内容组件中监听底部按钮提交事件,通常配合 form 属性和表单组件使用。

<script setup lang="ts">
import { onSubmit, useModal } from '@gopowerteam/modal-render'

const modal = useModal()

// 监听提交事件
onSubmit((actions) => {
  // 显示加载状态
  actions.showLoading()
  
  // 模拟异步操作
  setTimeout(() => {
    // 返回数据并关闭
    actions.close({ success: true })
  }, 1000)
})
</script>

<template>
  <form name="myForm">
    <!-- 表单内容 -->
  </form>
</template>

使用时在 open 的 options 中指定 form 属性:

modal.open(
  MyFormComponent,
  {},
  {
    footer: true,
    form: 'myForm',
    onOk({ close }) {
      // 触发 form 提交,会调用 onSubmit 回调
      close()
    },
  }
)

完整示例

示例 1:基础对话框

<!-- views/modal.vue -->
<script setup lang="ts">
import { useModal } from '@gopowerteam/modal-render'
import Test1 from '../components/test1.vue'

const modal = useModal()

function onClick() {
  const instance = modal.open(
    Test1,
    { content: 'asdasd' },
    {
      mode: 'dialog',
      title: 'asdasd',
      footer: true,
      submitText: '提交',
      onOk({ close }) {
        console.log('确认')
        close()
      },
      onCancel() {
        console.log('取消')
      },
      bodyStyle: {
        padding: '20px',
      },
    },
  )

  instance.then((data) => {
    console.log('返回数据:', data)
  })
}
</script>

<template>
  <button @click="onClick">打开弹窗</button>
</template>

示例 2:抽屉模式

modal.open(
  MyComponent,
  {},
  {
    mode: 'drawer',
    position: 'right', // left, top, bottom
    title: '右侧抽屉',
    footer: true,
  },
)

示例 3:表单提交

<!-- 调用方:views/UserModal.vue -->
<script setup lang="ts">
import { useModal } from '@gopowerteam/modal-render'
import UserForm from '../components/UserForm.vue'

const modal = useModal()

function openUserForm() {
  modal.open(
    UserForm,
    {},
    {
      title: '编辑用户',
      footer: true,
      form: 'userForm',
      width: 600,
      onOk({ close }) {
        // 触发 form 提交,会调用 onSubmit 回调
        // 实际关闭在 onSubmit 中处理
      },
    },
  )
}
</script>

<template>
  <button @click="openUserForm">编辑用户</button>
</template>
<!-- 表单组件:components/UserForm.vue -->
<script setup lang="ts">
import type { FieldRule } from '@arco-design/web-vue'
import { Form as AForm } from '@arco-design/web-vue'
import { onSubmit } from '@gopowerteam/modal-render'
import { reactive } from 'vue'

const formModel = reactive({
  name: '',
  email: '',
})

const formRules: Record<string, FieldRule> = {
  name: { required: true, message: '姓名不能为空' },
  email: [
    { required: true, message: '邮箱不能为空' },
    { type: 'email', message: '请输入正确的邮箱格式' },
  ],
}

// 监听提交事件
onSubmit((modal) => {
  modal.showLoading()
  // 模拟提交
  setTimeout(() => {
    modal.close(formModel)
  }, 1000)
})
</script>

<template>
  <AForm :model="formModel" name="userForm" :rules="formRules">
    <AFormItem field="name" label="姓名">
      <AInput v-model="formModel.name" />
    </AFormItem>
    <AFormItem field="email" label="邮箱">
      <AInput v-model="formModel.email" />
    </AFormItem>
  </AForm>
</template>

示例 4:嵌套模态框

<!-- views/test1.vue -->
<script setup lang="ts">
import { useModal } from '@gopowerteam/modal-render'
import Test2 from '../components/test2.vue'

const modal = useModal()

function onOpen() {
  modal.open(Test2, {}, { footer: true })
}
</script>

<template>
  <AButton type="primary" @click="onOpen">打开</AButton>
</template>
<!-- components/test2.vue -->
<script setup lang="ts">
import { useModal } from '@gopowerteam/modal-render'

const modal = useModal()

function openNested() {
  // 嵌套打开另一个模态框
  modal.open(
    AnotherComponent,
    {},
    { title: '嵌套弹窗', footer: true },
  )
}
</script>

<template>
  <div>
    <p>这是第一个模态框</p>
    <button @click="openNested">打开嵌套</button>
  </div>
</template>

高级用法

全屏模式

modal.open(
  MyComponent,
  {},
  {
    fullscreen: true,
    title: '全屏模态框',
  },
)

可拖拽对话框

modal.open(
  MyComponent,
  {},
  {
    draggable: true,
    title: '可拖拽',
  },
)

自定义背景色

modal.open(
  MyComponent,
  {},
  {
    backgroundColor: '#f0f2f5',
    bodyStyle: { padding: '20px' },
  },
)

ESC 键关闭

modal.open(
  MyComponent,
  {},
  {
    esc: true,
    closeable: true,
  },
)

点击遮罩关闭

modal.open(
  MyComponent,
  {},
  {
    maskClosable: true,
    title: '点击遮罩关闭',
  },
)

隐藏头部

modal.open(
  MyComponent,
  {},
  {
    header: false,
    footer: true,
    title: '无头部模态框',
  },
)

自定义尺寸

// 自定义宽度
modal.open(
  MyComponent,
  {},
  {
    width: 800,
    title: '固定宽度',
  },
)

// 自定义高度(抽屉模式)
modal.open(
  MyComponent,
  {},
  {
    mode: 'drawer',
    position: 'right',
    height: '80%',
    title: '自定义高度',
  },
)

// 使用预设尺寸
modal.open(
  MyComponent,
  {},
  {
    size: 'large',
    title: '大尺寸',
  },
)

挂载到 body

适用于模态框内容需要脱离当前容器上下文的场景:

<!-- App.vue -->
<ModalProvider :append-to-body="true">
  <RouterView />
</ModalProvider>

或者指定挂载位置:

<ModalProvider append-to-body="#modal-root">
  <App />
</ModalProvider>

示例:完整的表单提交流程

<!-- 调用方:ParentComponent.vue -->
<script setup lang="ts">
import { useModal } from '@gopowerteam/modal-render'
import UserForm from './UserForm.vue'

const modal = useModal()

function openForm() {
  modal.open(
    UserForm,
    {},
    {
      title: '新增用户',
      footer: true,
      form: 'userForm',
      width: 600,
      onOk({ close }) {
        // 点击确定时触发 form 提交,会调用 onSubmit 回调
        // 实际关闭在 onSubmit 中处理
      },
    },
  )
}
</script>

<template>
  <button @click="openForm">新增用户</button>
</template>
<!-- 表单组件:UserForm.vue -->
<script setup lang="ts">
import type { FieldRule } from '@arco-design/web-vue'
import { Form as AForm } from '@arco-design/web-vue'
import { onSubmit } from '@gopowerteam/modal-render'
import { reactive } from 'vue'

const formModel = reactive({
  name: '',
  email: '',
  phone: '',
})

const formRules: Record<string, FieldRule> = {
  name: { required: true, message: '请输入姓名' },
  email: [
    { required: true, message: '请输入邮箱' },
    { type: 'email', message: '邮箱格式不正确' },
  ],
  phone: { required: true, message: '请输入手机号' },
}

// 监听表单提交
onSubmit((actions) => {
  actions.showLoading({ text: '提交中...' })
  
  // 模拟异步提交
  setTimeout(() => {
    console.log('表单数据:', formModel)
    actions.close(formModel)
  }, 1500)
})
</script>

<template>
  <AForm :model="formModel" name="userForm" :rules="formRules" layout="vertical">
    <AFormItem field="name" label="姓名">
      <AInput v-model="formModel.name" placeholder="请输入姓名" />
    </AFormItem>
    <AFormItem field="email" label="邮箱">
      <AInput v-model="formModel.email" placeholder="请输入邮箱" />
    </AFormItem>
    <AFormItem field="phone" label="手机号">
      <AInput v-model="formModel.phone" placeholder="请输入手机号" />
    </AFormItem>
  </AForm>
</template>

样式自定义

CSS 变量

组件使用以下 CSS 变量,可通过覆盖全局样式自定义:

:root {
  --color-border-1: rgb(232, 232, 232);
  --color-text-2: #4e5969;
  --color-fill-1: #f5f5f5;
  --color-fill-3: #e5e6eb;
  --color-fill-4: #c9cdd4;
  --primary-5: rgb(28, 76, 207);
  --primary-6: rgb(45, 106, 251);
  --primary-7: rgb(14, 66, 210);
}

bodyStyle 属性

通过 bodyStyle 属性自定义内容区样式:

modal.open(
  MyComponent,
  {},
  {
    bodyStyle: {
      padding: '20px',
      backgroundColor: '#fff',
    },
  },
)

自定义主题

在全局样式中覆盖组件样式:

.modal-header {
  background-color: #f5f7fa;
}

.modal-footer button.submit-button {
  background-color: #409eff;
}

TypeScript 类型

SizeOptions

interface SizeOptions {
  small: string | number
  middle: string | number
  large: string | number
}

ShowLoadingOptions

interface ShowLoadingOptions {
  duration?: number  // 自动关闭时间(毫秒)
  text?: string      // 加载提示文字
}

OpenModalOptions

interface OpenModalOptions {
  closeable?: boolean
  esc?: boolean
  mask?: boolean
  maskClosable?: boolean
  title?: string
  header?: boolean
  footer?: boolean
  width?: number | string
  height?: number | string
  size?: 'large' | 'middle' | 'small'
  fullscreen?: boolean
  draggable?: boolean
  form?: string
  mode?: 'dialog' | 'drawer'
  position?: 'top' | 'right' | 'bottom' | 'left'
  backgroundColor?: string
  bodyStyle?: CSSProperties
  submitText?: string
  cancelText?: string
  onOk?: (options: { close: () => void }) => void
  onCancel?: () => void
}

ModalActions

interface ModalActions {
  open: (component: Component | 'confirm' | 'info' | 'warning' | 'error' | 'success', props?: Record<string, any>, options?: OpenModalOptions) => Promise<any> & { close: () => void }
  close: (id: string, data?: any) => void
  closeAll: () => void
  addEventListener: (id: string, event: 'submit', callback: (modal: ReturnType<typeof useModal>) => void) => void
  showLoading: (id?: string, options?: ShowLoadingOptions) => () => void
  hideLoading: (id?: string) => void
}

FAQ

1. 为什么 useModal 报错 "Not Found Modal Provider Component"?

useModal 必须在 ModalProvider 组件内部调用,确保组件树中存在 Provider:

<!-- 正确 -->
<template>
  <ModalProvider>
    <App />
  </ModalProvider>
</template>

2. 如何在模态框内部关闭?

两种方式:

// 方式1:在 onOk 回调中调用 close
onOk({ close }) {
  close()
}

// 方式2:使用 useModal
const modal = useModal()
modal.close()

3. 如何阻止模态框关闭?

不调用 close() 方法即可阻止关闭:

onOk({ close }) {
  validate().then(() => {
    close() // 只有验证通过才关闭
  })
}

4. 支持 SSR 吗?

支持,内部通过 clientMounted 处理了 SSR 场景。

5. 如何自定义按钮样式?

两种方式:

// 方式1:自定义提交按钮文字
modal.open(Component, {}, {
  submitText: '保存',
  cancelText: '放弃',
})

// 方式2:完全自定义底部
modal.open(Component, {}, {
  footer: false, // 隐藏默认底部
})

然后在组件内使用 ModalFooter 插槽自定义:

<template>
  <div>
    <ModalFooter>
      <button @click="handleCancel">取消</button>
      <button @click="handleSave">保存</button>
    </ModalFooter>
  </div>
</template>

6. 多个模态框如何区分?

通过 useModal() 在不同组件中调用会自动定位到当前所在模态框,无需手动管理。


注意事项

  1. 必须在 ModalProvider 内使用useModal 必须在 ModalProvider 组件树内调用
  2. 样式文件导入:记得在入口文件中导入 dist/style.css
  3. 表单提交:使用 onSubmit 时需要配合 form 属性指定表单 name
  4. Promise 行为:调用 close() 时传入的数据会作为 Promise 的 resolve 值
  5. 嵌套层级:组件会自动处理多层嵌套,无需额外配置

License

MIT