@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() 在不同组件中调用会自动定位到当前所在模态框,无需手动管理。
注意事项
- 必须在 ModalProvider 内使用:
useModal必须在ModalProvider组件树内调用 - 样式文件导入:记得在入口文件中导入
dist/style.css - 表单提交:使用
onSubmit时需要配合form属性指定表单 name - Promise 行为:调用
close()时传入的数据会作为 Promise 的 resolve 值 - 嵌套层级:组件会自动处理多层嵌套,无需额外配置
License
MIT
