Vue3对话框(Dialog)
2026/9/8 7:40:16 网站建设 项目流程

Vue2对话框(Dialog)

可自定义设置以下属性:

  • 对话框宽度(width),类型:string | number,单位 px,默认 520

  • 对话框高度(height),类型:string | number,单位 px,默认 'auto',自适应内容高度

  • 标题(title),类型:string | slot,默认 undefined

  • 自定义标题样式(titleStyle),类型:CSSProperties,默认 {}

  • 内容(content),类型:string | slot,默认 undefined

  • 自定义内容样式(contentStyle),类型:CSSProperties,默认 {}

  • 自定义 body 类名(bodyClass),类型:string,默认 undefined

  • 自定义 body 样式(bodyStyle),类型:CSSProperties,默认 {}

  • Scrollbar 组件属性配置(scrollbarProps),用于设置内容滚动条的样式,参考 Scrollbar Props,类型:object,默认 {}

  • 取消按钮文字(cancelText),类型:string,默认 '取消'

  • 取消按钮 props 配置(cancelProps),参考 Button 组件 Props,类型:object,默认 {}

  • 确定按钮文字(okText),类型:string,默认 '确认'

  • 确定按钮类型(okType),类型:'primary' | 'danger',默认 'primary'

  • 确认按钮 props 配置(okProps),优先级高于 okType,参考 Button 组件 Props,类型:object,默认 {}

  • 是否显示底部按钮(footer),类型:boolean | slot,默认 true

  • 关闭时是否销毁 Dialog 里的子元素(destroyOnClose),类型:boolean,默认 false

  • 是否允许切换全屏(switchFullscreen),允许后右上角会出现一个切换按钮,类型:boolean,默认 false

  • 是否水平垂直居中(centered),否则固定高度水平居中,类型:boolean,默认 false

  • 固定高度水平居中时,距顶部高度(top),仅当 centered: false 时生效,单位 px,类型:string | number,默认 100

  • 对话框动画出现的位置(transformOrigin),类型:'mouse' | 'center',默认 'mouse'

  • 确定按钮加载中(confirmLoading),类型:boolean,默认 false

  • 是否在打开对话框时禁用背景滚动(blockScroll),类型:boolean,默认 true

  • 是否支持键盘 esc 关闭(keyboard),类型:boolean,默认 true

  • 点击蒙层是否允许关闭(maskClosable),类型:boolean,默认 true

  • 自定义蒙层样式(maskStyle),类型:CSSProperties,默认 {}

  • 对话框是否可见(v-model:open),类型:boolean,默认 false

效果如下图:

在线预览

①创建对话框组件Dialog.vue:

其中引入使用了以下组件与工具函数:

  • Vue3滚动条(Scrollbar)
  • Vue3按钮(Button)
  • 否支持事件监听器选项 useOptionsSupported
<script setup lang="ts"> import { ref, computed, watch, watchEffect, nextTick, onMounted, onUnmounted } from 'vue' import type { CSSProperties } from 'vue' import Scrollbar from 'components/scrollbar' import Button from 'components/button' import { useOptionsSupported } from 'components/utils' export interface Props { width?: string | number // 对话框宽度,单位 px height?: string | number // 对话框高度,单位 px,默认自适应内容高度 title?: string // 标题 string | slot titleStyle?: CSSProperties // 自定义标题样式 content?: string // 内容 string | slot contentStyle?: CSSProperties // 自定义内容样式 bodyClass?: string // 自定义 body 类名 bodyStyle?: CSSProperties // 自定义 body 样式 scrollbarProps?: object // Scrollbar 组件属性配置,用于设置内容滚动条的样式 cancelText?: string // 取消按钮文字 cancelProps?: object // 取消按钮 props 配置,参考 Button 组件 Props okText?: string // 确定按钮文字 okType?: 'primary' | 'danger' // 确定按钮类型 okProps?: object // 确认按钮 props 配置,优先级高于 okType,参考 Button 组件 Props footer?: boolean // 是否显示底部按钮 boolean | slot destroyOnClose?: boolean // 关闭时是否销毁 Dialog 里的子元素 switchFullscreen?: boolean // 是否允许切换全屏,允许后右上角会出现一个切换按钮 centered?: boolean // 是否水平垂直居中,否则固定高度水平居中 top?: string | number // 固定高度水平居中时,距顶部高度,仅当 centered: false 时生效,单位 px transformOrigin?: 'mouse' | 'center' // 对话框动画出现的位置 confirmLoading?: boolean // 确定按钮 loading blockScroll?: boolean // 是否在打开对话框时禁用背景滚动 keyboard?: boolean // 是否支持键盘 esc 关闭 maskClosable?: boolean // 点击蒙层是否允许关闭 maskStyle?: CSSProperties // 自定义蒙层样式 open?: boolean // 对话框是否可见 } const props = withDefaults(defineProps<Props>(), { width: 520, height: 'auto', title: undefined, titleStyle: () => ({}), content: undefined, contentStyle: () => ({}), bodyClass: undefined, bodyStyle: () => ({}), scrollbarProps: () => ({}), cancelText: '取消', cancelProps: () => ({}), okText: '确定', okType: 'primary', okProps: () => ({}), footer: true, destroyOnClose: false, switchFullscreen: false, centered: false, top: 100, transformOrigin: 'mouse', confirmLoading: false, blockScroll: true, keyboard: true, maskClosable: true, maskStyle: () => ({}), open: false }) const dialogRef = ref() // dialog DOM 引用 const mousePosition = ref<{ x: number; y: number } | null>(null) // 鼠标点击位置 const dialogOpen = ref<boolean>() const showDialogWrap = ref<boolean>() const dialogTransformOrigin = ref<string>('50% 50%') const fullscreen = ref<boolean>(false) const { isSupported: captureSupported } = useOptionsSupported('capture') const emits = defineEmits(['update:open', 'cancel', 'ok']) const dialogWidth = computed(() => { if (typeof props.width === 'number') { return `${props.width}px` } return props.width }) const dialogHeight = computed(() => { if (typeof props.height === 'number') { return `${props.height}px` } return props.height }) const dialogTop = computed(() => { if (typeof props.top === 'number') { return `${props.top}px` } return props.top }) const dialogStyle = computed(() => { if (fullscreen.value) { if (props.transformOrigin === 'mouse') { return { width: '100%', transformOrigin: `${mousePosition.value?.x}px ${mousePosition.value?.y}px` } as CSSProperties } else { return { width: '100%', transformOrigin: dialogTransformOrigin.value } as CSSProperties } } else { if (props.centered) { return { width: dialogWidth.value, transformOrigin: dialogTransformOrigin.value } as CSSProperties } else { return { width: dialogWidth.value, transformOrigin: dialogTransformOrigin.value, top: dialogTop.value } as CSSProperties } } }) const dialogBodyStyle = computed(() => { if (fullscreen.value) { return { height: '100vh', ...props.bodyStyle } as CSSProperties } else { return { height: dialogHeight.value, ...props.bodyStyle } as CSSProperties } }) watch( dialogOpen, async (to) => { if (to) { await nextTick() dialogRef.value.focus() if (props.blockScroll) { // 锁定滚动 document.documentElement.style.overflowY = 'hidden' document.body.style.overflowY = 'hidden' } } else { if (props.blockScroll) { // 解锁滚动 document.documentElement.style.removeProperty('overflow-y') document.body.style.removeProperty('overflow-y') } } }, { immediate: true } ) watchEffect(() => { dialogOpen.value = props.open }) onMounted(() => { document.addEventListener('click', getClickPosition, captureSupported.value ? { capture: true } : true) // 事件在捕获阶段执行 }) onUnmounted(() => { document.removeEventListener('click', getClickPosition, captureSupported.value ? { capture: true } : true) }) function getClickPosition(e: MouseEvent) { if (!dialogOpen.value) { mousePosition.value = { x: e.clientX, // 相对于浏览器视口左上角的 X 坐标,不页面滚动而改变 y: e.clientY // 相对于浏览器视口左上角的 Y 坐标,不页面滚动而改变 } } } async function onBeforeEnter(el: Element) { showDialogWrap.value = true await nextTick() if (props.transformOrigin === 'mouse' && mousePosition.value) { const rect = el.getBoundingClientRect() dialogTransformOrigin.value = `${mousePosition.value.x - rect.left}px ${mousePosition.value.y - rect.top}px` } else { dialogTransformOrigin.value = '50% 50%' } } function onBeforeLeave(el: Element) { if (props.transformOrigin === 'mouse' && mousePosition.value) { const rect = el.getBoundingClientRect() dialogTransformOrigin.value = `${mousePosition.value.x - rect.left}px ${mousePosition.value.y - rect.top}px` } else { dialogTransformOrigin.value = '50% 50%' } } function onAfterLeave() { showDialogWrap.value = false // 重置全屏显示 fullscreen.value = false } function onFullScreen() { fullscreen.value = !fullscreen.value } function onCancel() { dialogOpen.value = false emits('update:open', false) emits('cancel') } function onOk() { emits('ok') } </script> <template> <div class="dialog-root"> <Transition name="fade"> <div v-show="dialogOpen" class="dialog-mask" :style="maskStyle"></div> </Transition> <div v-show="showDialogWrap" tabindex="-1" ref="dialogRef" class="dialog-wrap" :class="{ 'flex-centered': centered }" @click.self="props.maskClosable ? onCancel() : () => false" @keydown.esc="props.keyboard ? onCancel() : () => false" > <Transition name="zoom" enter-from-class="zoom-enter" enter-active-class="zoom-enter" enter-to-class="zoom-enter zoom-enter-active" leave-from-class="zoom-leave" leave-active-class="zoom-leave zoom-leave-active" leave-to-class="zoom-leave zoom-leave-active" @before-enter="onBeforeEnter" @before-leave="onBeforeLeave" @after-leave="onAfterLeave" > <div v-show="dialogOpen" class="dialog-container" :class="{ 'dialog-with-fullscreen': fullscreen }" :style="dialogStyle" > <div v-if="!destroyOnClose" class="dialog-body-wrap" :class="bodyClass" :style="dialogBodyStyle"> <div class="dialog-header" :class="{ 'header-with-switch': switchFullscreen }" :style="titleStyle"> <slot name="title">{{ title }}</slot> </div> <span v-if="switchFullscreen" class="fullscreen-action" @click="onFullScreen"> <svg v-show="!fullscreen" focusable="false" ><script setup lang="ts"> import Dialog from './Dialog.vue' import { ref } from 'vue' const open1 = ref(false) const open2 = ref(false) const open3 = ref(false) const open4 = ref(false) const open5 = ref(false) const open6 = ref(false) const open7 = ref(false) const open8 = ref(false) const open9 = ref(false) const open10 = ref(false) const open11 = ref(false) const open12 = ref(false) const open13 = ref(false) const confirmLoading = ref(false) function onCancel() { // 点击蒙层或 Esc 键或右上角叉或取消按钮的回调 console.log('cancel') } function onOk() { // 点击确定的回调 open1.value = false open2.value = false open3.value = false open4.value = false open5.value = false open6.value = false open7.value = false open8.value = false open9.value = false open10.value = false open11.value = false open12.value = false console.log('ok') } function handleCancel() { // 点击蒙层或 Esc 键或右上角叉或取消按钮的回调 console.log('cancel') } function handleOk() { // 点击确定回调 confirmLoading.value = true // 开启加载状态 setTimeout(() => { confirmLoading.value = false open13.value = false console.log('ok') }, 2000) } </script> <template> <div> <h1>{{ $route.name }} {{ $route.meta.title }}</h1> <h2 class="mt30 mb10">基本使用</h2> <Button type="primary" @click="open1 = true">Open Dialog</Button> <Dialog v-model:open="open1" title="Title" @cancel="onCancel" @ok="onOk"> <p>Bla bla ...</p> <p>Bla bla ...</p> <p>Bla bla ...</p> </Dialog> <h2 class="mt30 mb10">自定义宽高</h2> <Button type="primary" @click="open2 = true">Open Dialog</Button> <Dialog v-model:open="open2" :width="480" :height="200" @cancel="onCancel" @ok="onOk"> <template #title>Title</template> <p>Some contents...</p> <p>Some contents...</p> <p>Some contents...</p> <p>Some contents...</p> <p>Some contents...</p> <p>Some contents...</p> </Dialog> <h2 class="mt30 mb10">自定义样式</h2> <Space> <Button type="primary" @click="open3 = true">Custom Body Class Dialog</Button> <Button type="primary" @click="open4 = true">Custom Body & Mask Style Dialog</Button> <Button type="primary" @click="open5 = true">Custom Title & Content Style Dialog</Button> </Space> <Dialog v-model:open="open3" title="Title" body-class="custom-class" @cancel="onCancel" @ok="onOk"> <p>Bla bla ...</p> <p>Bla bla ...</p> <p>Bla bla ...</p> </Dialog> <Dialog v-model:open="open4" title="Title" :body-style="{ padding: '24px', borderRadius: '16px' }" :mask-style="{ backgroundColor: 'rgba(0, 0, 0, 0.6)' }" @cancel="onCancel" @ok="onOk" > <p>Bla bla ...</p> <p>Bla bla ...</p> <p>Bla bla ...</p> </Dialog> <Dialog v-model:open="open5" title="Title" :title-style="{ fontSize: '18px', color: '#d4380d' }" :content-style="{ fontSize: '16px', color: '#d4380d' }" @cancel="onCancel" @ok="onOk" > <p>Bla bla ...</p> <p>Bla bla ...</p> <p>Bla bla ...</p> </Dialog> <h2 class="mt30 mb10">自定义按钮</h2> <Button type="primary" @click="open6 = true">Custom Btns Dialog</Button> <Dialog v-model:open="open6" title="Title" cancel-text="Return" :cancel-props="{ type: 'danger', ghost: true }" ok-text="Submit" :ok-props="{ type: 'primary', ghost: true }" @cancel="onCancel" @ok="onOk" > <p>Bla bla ...</p> <p>Bla bla ...</p> <p>Bla bla ...</p> </Dialog> <h2 class="mt30 mb10">隐藏底部按钮</h2> <Button type="primary" @click="open7 = true">Open Dialog</Button> <Dialog v-model:open="open7" title="Title" :footer="false" @cancel="onCancel" @ok="onOk"> <p>Bla bla ...</p> <p>Bla bla ...</p> <p>Bla bla ...</p> </Dialog> <h2 class="mt30 mb10">切换全屏</h2> <Button type="primary" @click="open8 = true">Open Dialog</Button> <Dialog v-model:open="open8" title="Title" switch-fullscreen @cancel="onCancel" @ok="onOk"> <p>Bla bla ...</p> <p>Bla bla ...</p> <p>Bla bla ...</p> </Dialog> <h2 class="mt30 mb10">自定义位置</h2> <Space> <Button type="primary" @click="open9 = true">Fixed Top Number Dialog</Button> <Button type="primary" @click="open10 = true">Fixed Top Percent Dialog</Button> <Button type="primary" @click="open11 = true">Vertically Centered Dialog</Button> </Space> <Dialog v-model:open="open9" title="60px Top Title" :top="60" @cancel="onCancel" @ok="onOk"> <p>Bla bla ...</p> <p>Bla bla ...</p> <p>Bla bla ...</p> </Dialog> <Dialog v-model:open="open10" title="20% Top Title" top="20%" @cancel="onCancel" @ok="onOk"> <p>Bla bla ...</p> <p>Bla bla ...</p> <p>Bla bla ...</p> </Dialog> <Dialog v-model:open="open11" title="Centered Title" centered @cancel="onCancel" @ok="onOk"> <p>Bla bla ...</p> <p>Bla bla ...</p> <p>Bla bla ...</p> </Dialog> <h2 class="mt30 mb10">动画出现位置</h2> <Button type="primary" @click="open12 = true">Transform Origin Center Dialog</Button> <Dialog v-model:open="open12" title="Title" transform-origin="center" @cancel="onCancel" @ok="onOk"> <p>Bla bla ...</p> <p>Bla bla ...</p> <p>Bla bla ...</p> </Dialog> <h2 class="mt30 mb10">异步延迟关闭</h2> <Button type="primary" @click="open13 = true">Delayed Close Dialog</Button> <Dialog v-model:open="open13" title="Title" :confirm-loading="confirmLoading" @cancel="handleCancel" @ok="handleOk"> <p>Bla bla ...</p> <p>Bla bla ...</p> <p>Bla bla ...</p> </Dialog> </div> </template> <style lang="less" scoped> :deep(.custom-class) { .dialog-header { color: #ff6900 !important; } .dialog-content { color: #ff6900 !important; } } </style>

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询