Dialog 弹窗和抽屉组件
概述
Dialog 组件是一套基于 Element Plus 的弹窗和抽屉组件封装,提供统一的 API 和样式,支持对话框和抽屉两种模式。通过简单的配置即可快速创建弹窗或抽屉界面。
快速开始
1. 引入组件
import { useDialog } from '@/components/dialog'
2. 创建弹窗
const [Dialog, dialogApi] = useDialog({
title: '弹窗标题',
width: '50%',
onConfirm: () => {
// 确定按钮回调
},
onCancel: () => {
// 取消按钮回调
}
})
3. 在模板中使用
<template>
<Dialog>
<div>弹窗内容</div>
</Dialog>
</template>
4. 控制弹窗显示
const show = () => {
dialogApi.open()
}
const hide = () => {
dialogApi.close()
}
核心概念
useDialog 配置参数
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
title | string | '操作' | 弹窗标题 |
customClass | string | 'm-dialog' | 自定义样式类名 |
destroyOnClose | boolean | true | 关闭时销毁弹窗内容 |
closeOnClickModal | boolean | false | 点击遮罩层关闭弹窗 |
closeOnPressEscape | boolean | true | 按 ESC 键关闭弹窗 |
width | string | number | '60%' | 弹窗宽度 |
fullscreen | boolean | false | 是否全屏 |
showClose | boolean | true | 是否显示关闭按钮 |
loading | boolean | false | 内容区域加载状态 |
confirmLoading | boolean | false | 确定按钮加载状态 |
showConfirmButton | boolean | true | 是否显示确定按钮 |
showCancelButton | boolean | true | 是否显示取消按钮 |
footer | boolean | true | 是否显示底部操作栏 |
onConfirm | () => void | - | 确定按钮回调函数 |
onCancel | () => void | - | 取消按钮回调函数 |
dialogType | 'dialog' | 'drawer' | 'dialog' | 弹窗类型 |
direction | 'ltr' | 'rtl' | 'ttb' | 'btt' | 'rtl' | 抽屉打开方向 |
modal | boolean | true | 是否需要遮罩层 |
dialogApi 方法
通过 useDialog
返回的 dialogApi
提供以下方法:
方法名 | 参数 | 返回值 | 说明 |
---|---|---|---|
open | - | void | 打开弹窗 |
close | - | void | 关闭弹窗 |
setState | state: MyDialogProps | Promise<void> | 动态设置弹窗属性 |
getState | - | Promise<MyDialogProps> | 获取弹窗当前属性 |
使用示例
基础弹窗
<template>
<div>
<el-button @click="showDialog">打开弹窗</el-button>
<Dialog>
<div style="padding: 20px">
<p>这是弹窗内容</p>
</div>
</Dialog>
</div>
</template>
<script setup lang="ts">
import { useDialog } from '@/components/dialog'
const [Dialog, dialogApi] = useDialog({
title: '基础弹窗',
width: '400px',
onConfirm: () => {
console.log('点击了确定')
dialogApi.close()
},
onCancel: () => {
console.log('点击了取消')
dialogApi.close()
}
})
const showDialog = () => {
dialogApi.open()
}
</script>
抽屉模式
<template>
<div>
<el-button @click="showDrawer">打开抽屉</el-button>
<Dialog>
<div style="padding: 20px">
<p>这是抽屉内容</p>
<p>抽屉从右侧滑入</p>
</div>
</Dialog>
</div>
</template>
<script setup lang="ts">
import { useDialog } from '@/components/dialog'
const [Dialog, dialogApi] = useDialog({
title: '抽屉示例',
dialogType: 'drawer',
direction: 'rtl', // 从右侧滑入
width: '400px',
onConfirm: () => {
console.log('点击了确定')
dialogApi.close()
}
})
const showDrawer = () => {
dialogApi.open()
}
</script>
表单弹窗
<template>
<div>
<el-button @click="showFormDialog">打开表单弹窗</el-button>
<Dialog>
<BasicForm ref="formRef" />
</Dialog>
</div>
</template>
<script setup lang="ts">
import { useDialog } from '@/components/dialog'
import { useForm } from '@/components/form'
const formSchema = [
{
prop: 'name',
label: '姓名',
component: 'Input',
rules: [{ required: true, message: '请输入姓名', trigger: 'blur' }]
},
{
prop: 'email',
label: '邮箱',
component: 'Input'
}
]
const [BasicForm, formApi] = useForm(formSchema)
const [Dialog, dialogApi] = useDialog({
title: '新增用户',
width: '500px',
confirmLoading: false,
onConfirm: () => {
handleSubmit()
}
})
const showFormDialog = () => {
dialogApi.open()
// 重置表单
nextTick(() => {
formApi.resetFields()
})
}
const handleSubmit = () => {
dialogApi.setState({ confirmLoading: true })
formApi
.validate()
.then(async () => {
const values = await formApi.getValues()
console.log('表单数据:', values)
// 这里可以调用接口保存数据
dialogApi.close()
})
.catch(() => {
console.log('表单验证失败')
})
.finally(() => {
dialogApi.setState({ confirmLoading: false })
})
}
</script>
详情抽屉
<template>
<div>
<el-button @click="showDetailDrawer">查看详情</el-button>
<Dialog>
<DetailForm :model="detailData" />
</Dialog>
</div>
</template>
<script setup lang="ts">
import { useDialog } from '@/components/dialog'
import { useDetailForm } from '@/components/form'
const detailSchema = [
{
prop: 'name',
label: '姓名',
component: 'Input'
},
{
prop: 'email',
label: '邮箱',
component: 'Input'
}
]
const detailData = ref({
name: '张三',
email: 'zhangsan@example.com'
})
const [DetailForm] = useDetailForm(detailSchema)
const [Dialog, dialogApi] = useDialog({
title: '用户详情',
dialogType: 'drawer',
direction: 'rtl',
width: '400px',
footer: false // 不显示底部操作栏
})
const showDetailDrawer = () => {
dialogApi.open()
}
</script>
动态控制弹窗属性
<template>
<div>
<el-button @click="showDialog">打开弹窗</el-button>
<Dialog>
<div style="padding: 20px">
<el-button @click="toggleFullscreen">切换全屏</el-button>
<el-button @click="changeTitle">修改标题</el-button>
<el-button @click="showLoading">显示加载</el-button>
</div>
</Dialog>
</div>
</template>
<script setup lang="ts">
import { useDialog } from '@/components/dialog'
const [Dialog, dialogApi] = useDialog({
title: '动态控制弹窗',
width: '500px'
})
const showDialog = () => {
dialogApi.open()
}
const toggleFullscreen = () => {
dialogApi.getState().then((state) => {
dialogApi.setState({
fullscreen: !state.fullscreen
})
})
}
const changeTitle = () => {
dialogApi.setState({
title: '修改后的标题'
})
}
const showLoading = () => {
dialogApi.setState({
loading: true
})
// 2秒后关闭加载状态
setTimeout(() => {
dialogApi.setState({
loading: false
})
}, 2000)
}
</script>
插槽使用
Dialog 组件提供多个插槽来自定义底部操作栏:
<template>
<Dialog>
<div style="padding: 20px">
<p>弹窗内容</p>
</div>
<!-- 自定义底部操作栏 -->
<template #footer>
<el-button @click="handleCustomAction">自定义操作</el-button>
<el-button @click="dialogApi.close">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</template>
<!-- 或者使用特定位置的插槽 -->
<template #prepend-footer>
<el-button>插入到最前面</el-button>
</template>
<template #center-footer>
<el-button>插入到中间</el-button>
</template>
<template #append-footer>
<el-button>插入到最后面</el-button>
</template>
</Dialog>
</template>
可用插槽列表:
插槽名 | 说明 |
---|---|
default | 弹窗主要内容区域 |
footer | 底部操作栏整体 |
prepend-footer | 底部操作栏前面 |
center-footer | 底部操作栏中间 |
append-footer | 底部操作栏后面 |
完整表单弹窗示例
<template>
<Dialog>
<BasicForm :model="formModel">
<!-- 可以使用插槽自定义某些字段 -->
<template #customField="{ model }">
<el-input v-model="model.customField" placeholder="自定义字段" />
</template>
</BasicForm>
</Dialog>
</template>
<script setup lang="ts">
import { useForm } from '@/components/form'
import { useDialog } from '@/components/dialog'
const record = ref({})
const updateFlag = ref(false)
const formModel = reactive<any>({})
const formSchema = [
{
prop: 'name',
label: '姓名',
component: 'Input',
colSpan: 12,
rules: [{ required: true, message: '请输入姓名', trigger: 'blur' }]
},
{
prop: 'email',
label: '邮箱',
component: 'Input',
colSpan: 12
}
]
const [BasicForm, formApi] = useForm(formSchema)
const [Dialog, dialogApi] = useDialog({
title: '用户管理',
destroyOnClose: true,
onConfirm: () => {
handleSubmit()
}
})
const show = ({ data, isUpdate }: { data: any; isUpdate: boolean }) => {
updateFlag.value = isUpdate === true
record.value = data || {}
dialogApi.open()
dialogApi.setState({
title: updateFlag.value ? '修改用户' : '新增用户'
})
nextTick(() => {
formApi.setValues(record.value)
})
}
const handleSubmit = () => {
dialogApi.setState({ loading: true, confirmLoading: true })
formApi
.validate()
.then(async () => {
const values = await formApi.getValues()
console.log(values)
// 在这里处理提交逻辑
dialogApi.close()
})
.finally(() => {
dialogApi.setState({ loading: false, confirmLoading: false })
})
}
defineExpose({
show
})
</script>
注意事项
- 使用
useDialog
创建的弹窗组件需要通过dialogApi.open()
方法手动打开 - 弹窗关闭时默认会销毁内容,如需保留状态可设置
destroyOnClose: false
- 抽屉模式通过设置
dialogType: 'drawer'
启用 - 可以通过
setState
方法动态修改弹窗属性 - 确定和取消按钮的回调需要手动处理弹窗关闭逻辑
- 表单弹窗建议在打开时重置表单状态
- 加载状态可通过
loading
(内容区域)和confirmLoading
(确定按钮)分别控制