1. 创建 Dialog 组件
src/components/CommandDialog.vue
<template>
<transition name="fade">
<div v-if="visible" class="command-dialog-overlay" @click.self="handleOverlayClick">
<div class="command-dialog" :style="dialogStyle">
<div class="command-dialog-header">
<h3>{{ title }}</h3>
<button class="close-btn" @click="handleClose">×</button>
</div>
<div class="command-dialog-body">
<div v-if="typeof content === 'string'" v-html="content"></div>
<component v-else-if="typeof content === 'object'" :is="content"></component>
<div v-else>
{{ content }}
</div>
</div>
<div class="command-dialog-footer">
<button v-for="btn in buttons"
:key="btn.text"
:class="btn.className || 'default-btn'"
@click="() => handleButtonClick(btn)"
:style="btn.style">
{{ btn.text }}
</button>
</div>
</div>
</div>
</transition>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
title: {
type: String,
default: '提示'
},
content: {
type: [String, Object],
default: ''
},
width: {
type: String,
default: '400px'
},
buttons: {
type: Array,
default: () => [
{ text: '确定', className: 'primary-btn', handler: 'confirm' }
]
},
showClose: {
type: Boolean,
default: true
},
closeOnClickOverlay: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['close', 'confirm', 'cancel'])
const visible = ref(false)
const dialogStyle = computed(() => ({
width: props.width
}))
const show = () => {
visible.value = true
}
const hide = () => {
visible.value = false
emit('close')
}
const handleClose = () => {
hide()
emit('cancel')
}
const handleOverlayClick = () => {
if (props.closeOnClickOverlay) {
handleClose()
}
}
const handleButtonClick = (btn) => {
if (btn.handler === 'confirm') {
emit('confirm')
hide()
} else if (btn.handler === 'cancel') {
emit('cancel')
hide()
} else if (typeof btn.handler === 'function') {
const result = btn.handler()
if (result !== false) {
hide()
}
} else {
hide()
}
}
defineExpose({
show,
hide
})
</script>
<style scoped>
.command-dialog-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.command-dialog {
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
overflow: hidden;
}
.command-dialog-header {
padding: 16px 20px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.command-dialog-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.close-btn:hover {
color: #333;
}
.command-dialog-body {
padding: 20px;
max-height: 60vh;
overflow-y: auto;
}
.command-dialog-footer {
padding: 16px 20px;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 12px;
}
.default-btn, .primary-btn {
padding: 8px 16px;
border-radius: 4px;
border: 1px solid #ddd;
background: white;
cursor: pointer;
font-size: 14px;
}
.primary-btn {
background: #409eff;
border-color: #409eff;
color: white;
}
.primary-btn:hover {
background: #66b1ff;
}
.default-btn:hover {
background: #f5f7fa;
}
/* 动画效果 */
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
.fade-enter-active .command-dialog,
.fade-leave-active .command-dialog {
transition: transform 0.3s, opacity 0.3s;
}
.fade-enter-from .command-dialog,
.fade-leave-to .command-dialog {
transform: translateY(-20px);
opacity: 0;
}
</style>
2. 创建命令式 Dialog 管理工具
src/utils/dialog.js
import { createApp, h } from 'vue'
import CommandDialog from '@/components/CommandDialog.vue'
class DialogManager {
constructor() {
this.instance = null
this.container = null
this.currentDialog = null
}
// 创建 Dialog 实例
createDialog(options) {
return new Promise((resolve, reject) => {
if (this.currentDialog) {
this.currentDialog.hide()
}
// 创建容器
const container = document.createElement('div')
document.body.appendChild(container)
// 创建 Vue 应用
const app = createApp({
setup() {
const dialogRef = ref(null)
const onClose = () => {
resolve({ action: 'close' })
destroyDialog()
}
const onConfirm = () => {
resolve({ action: 'confirm' })
destroyDialog()
}
const onCancel = () => {
resolve({ action: 'cancel' })
destroyDialog()
}
const showDialog = () => {
nextTick(() => {
dialogRef.value?.show()
})
}
onMounted(() => {
showDialog()
})
// 销毁对话框
const destroyDialog = () => {
setTimeout(() => {
app.unmount()
if (container.parentNode) {
container.parentNode.removeChild(container)
}
this.currentDialog = null
}, 300)
}
return () => h(CommandDialog, {
ref: dialogRef,
...options,
onClose,
onConfirm,
onCancel
})
}
})
this.currentDialog = app.mount(container)
this.instance = app
this.container = container
})
}
// 快捷方法:确认对话框
async confirm(options) {
const defaultOptions = {
title: '确认',
content: options.content || '确定执行此操作吗?',
buttons: [
{ text: '取消', className: 'default-btn', handler: 'cancel' },
{ text: '确定', className: 'primary-btn', handler: 'confirm' }
]
}
const result = await this.createDialog({
...defaultOptions,
...options
})
return result.action === 'confirm'
}
// 快捷方法:提示对话框
async alert(options) {
const defaultOptions = {
title: '提示',
content: options.content || '',
buttons: [
{ text: '确定', className: 'primary-btn', handler: 'confirm' }
]
}
await this.createDialog({
...defaultOptions,
...options
})
}
// 快捷方法:自定义内容对话框
async custom(options) {
return await this.createDialog(options)
}
}
// 创建单例
const dialogManager = new DialogManager()
// 导出命令式 API
export const $dialog = {
/**
* 确认对话框
* @param {Object|String} options - 配置对象或内容字符串
*/
confirm(options) {
if (typeof options === 'string') {
return dialogManager.confirm({ content: options })
}
return dialogManager.confirm(options)
},
/**
* 提示对话框
* @param {Object|String} options - 配置对象或内容字符串
*/
alert(options) {
if (typeof options === 'string') {
return dialogManager.alert({ content: options })
}
return dialogManager.alert(options)
},
/**
* 自定义对话框
* @param {Object} options - 配置对象
*/
custom(options) {
return dialogManager.custom(options)
},
/**
* 复杂对话框(使用组件)
* @param {Object} options - 配置对象
*/
component(options) {
return dialogManager.custom({
...options,
content: options.component
})
}
}
// 作为 Vue 插件安装
export default {
install(app) {
app.config.globalProperties.$dialog = $dialog
app.provide('dialog', $dialog)
}
}
3. 注册插件
main.js
import { createApp } from 'vue'
import App from './App.vue'
import dialogPlugin from './utils/dialog'
const app = createApp(App)
// 安装 Dialog 插件
app.use(dialogPlugin)
app.mount('#app')
4. 使用示例
4.1 在组件中使用
<template>
<div>
<button @click="showConfirmDialog">确认对话框</button>
<button @click="showAlertDialog">提示对话框</button>
<button @click="showCustomDialog">自定义对话框</button>
<button @click="showComponentDialog">组件对话框</button>
</div>
</template>
<script setup>
import { inject, h } from 'vue'
// 方法1:通过 inject
const dialog = inject('dialog')
// 方法2:通过全局属性(在模板中直接使用 $dialog)
const showConfirmDialog = async () => {
const result = await dialog.confirm({
title: '删除确认',
content: '确定要删除这条数据吗?',
width: '500px'
})
if (result) {
console.log('用户点击了确定')
// 执行删除操作
} else {
console.log('用户点击了取消')
}
}
const showAlertDialog = async () => {
await dialog.alert({
title: '操作成功',
content: '数据已保存成功!',
buttons: [
{ text: '知道了', className: 'primary-btn' }
]
})
}
const showCustomDialog = async () => {
const result = await dialog.custom({
title: '自定义操作',
content: '<p>请选择操作:</p>',
buttons: [
{ text: '选项一', className: 'default-btn' },
{ text: '选项二', className: 'default-btn' },
{ text: '选项三', className: 'primary-btn' }
]
})
console.log('用户操作:', result)
}
// 使用组件作为内容
const CustomContent = {
setup() {
const count = ref(0)
return () => h('div', [
h('p', '这是一个自定义组件'),
h('p', `计数: ${count.value}`),
h('button', {
onClick: () => count.value++
}, '增加计数')
])
}
}
const showComponentDialog = async () => {
await dialog.component({
title: '组件对话框',
component: CustomContent,
width: '600px',
buttons: [
{ text: '关闭', className: 'default-btn' }
]
})
}
</script>
4.2 在 Options API 中使用
export default {
methods: {
async showDialog() {
const result = await this.$dialog.confirm('确定要删除吗?')
if (result) {
// 处理确认逻辑
}
}
}
}
5. TypeScript 支持
src/types/dialog.d.ts
declare module 'vue' {
interface ComponentCustomProperties {
$dialog: {
confirm(options: DialogOptions | string): Promise<boolean>
alert(options: DialogOptions | string): Promise<void>
custom(options: DialogOptions): Promise<{ action: string }>
component(options: ComponentDialogOptions): Promise<{ action: string }>
}
}
}
interface DialogOptions {
title?: string
content?: string | object
width?: string
buttons?: DialogButton[]
showClose?: boolean
closeOnClickOverlay?: boolean
}
interface ComponentDialogOptions extends DialogOptions {
component: any
}
interface DialogButton {
text: string
className?: string
style?: Record<string, string>
handler?: 'confirm' | 'cancel' | Function
}
export {}
特性说明
命令式调用:无需在模板中声明,直接通过 API 调用
Promise 支持:所有方法返回 Promise,方便处理用户操作
灵活的按钮配置:支持自定义按钮和回调
组件支持:可以使用 Vue 组件作为对话框内容
动画效果:内置渐入渐出动画
响应式设计:支持移动端适配
Typescript 支持:完整的类型提示
这个实现使用了 Vue 3 的 Composition API 和 h 函数,提供了完整的命令式 Dialog 解决方案。