feat(components): 新增 NovaDialog 通用弹框组件

- 基于 n-modal 封装的统一弹框组件
- 支持自定义宽度、高度、标题和按钮文字
- 提供 novaOpen/novaClose 方法控制显示状态
- 支持 #header 和 #content 插槽自定义内容
- 统一项目弹框交互体验和视觉风格
This commit is contained in:
Leo 2025-07-06 18:14:26 +08:00
parent b8d6e5d781
commit 02778d7c4f

View File

@ -0,0 +1,223 @@
<script setup lang="ts">
import { ref, toRefs } from 'vue'
import { NButton, NSpace } from 'naive-ui'
//
interface IDialogProps {
/** 弹框标题 */
title?: string
/** 弹框宽度 */
width?: string | number
/** 内容区域高度 */
height?: string | number
/** 确认按钮文本 */
confirmText?: string
/** 取消按钮文本 */
cancelText?: string
/** 全屏显示 */
fullscreen?: boolean
/** 确认按钮加载状态 */
loading?: boolean
/** 隐藏底部按钮 */
footerHidden?: boolean
/** 自动聚焦 */
autoFocus?: boolean
/** 居中显示 */
center?: boolean
}
//
const props = withDefaults(defineProps<IDialogProps>(), {
title: '提示',
width: 500,
height: 'auto',
confirmText: '确定',
cancelText: '取消',
fullscreen: false,
loading: false,
footerHidden: false,
autoFocus: true,
center: true,
})
//
const emits = defineEmits<{
novaConfirm: []
novaCancel: []
novaClose: []
}>()
//
const visible = ref(false)
// 使toRefs
const { loading } = toRefs(props)
const confirmLoading = ref(loading)
/** 打开弹框 */
function novaOpen() {
visible.value = true
}
/** 关闭弹框 */
function novaClose() {
visible.value = false
}
/** 快速关闭弹框 */
function novaQuickClose() {
visible.value = false
}
/** 确认事件处理 */
function handleConfirm() {
emits('novaConfirm')
}
/** 取消事件处理 */
function handleCancel() {
emits('novaCancel')
}
/** 关闭事件处理 */
function handleClose() {
visible.value = false
emits('novaClose')
}
/** 暴露给父组件的方法 */
defineExpose({
novaOpen,
novaClose,
novaQuickClose,
})
</script>
<template>
<n-modal
v-model:show="visible"
:mask-closable="false"
:close-on-esc="false"
:auto-focus="autoFocus"
preset="card"
:loading="confirmLoading"
:show-icon="false"
:style="{
width: typeof width === 'number' ? `${width}px` : width,
}"
class="nova-dialog"
@close="handleClose"
>
<!-- 头部插槽 -->
<template #header>
<div v-if="$slots.header" class="nova-dialog-custom-header">
<slot name="header" />
</div>
<div v-else class="nova-dialog-header">
<div class="nova-dialog-title">
{{ title }}
</div>
</div>
</template>
<!-- 内容区域 -->
<div
class="nova-dialog-content"
:style="{
height: fullscreen ? 'auto' : (typeof height === 'number' ? `${height}px` : height),
overflow: height === 'auto' ? 'visible' : 'auto',
}"
>
<slot name="content" />
</div>
<!-- 底部操作区域 -->
<template #footer>
<div v-if="!footerHidden" class="nova-dialog-footer">
<NSpace justify="center">
<NButton
size="medium"
@click="handleCancel"
>
{{ cancelText }}
</NButton>
<NButton
type="primary"
size="medium"
:loading="confirmLoading"
@click="handleConfirm"
>
{{ confirmText }}
</NButton>
</NSpace>
</div>
<div v-else />
</template>
</n-modal>
</template>
<style scoped>
/* 弹框标题样式 */
.nova-dialog :deep(.n-card-header) {
padding: 0;
border-bottom: none;
background: transparent;
}
.nova-dialog-header {
padding: 12px 16px 8px;
background: white;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.nova-dialog-title {
margin: 0;
font-size: 16px;
font-weight: 500;
color: #1a1a1a;
line-height: 1.4;
text-align: center;
}
.nova-dialog-custom-header {
padding: 12px 16px 8px;
background: white;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
}
/* 弹框整体样式 */
.nova-dialog :deep(.n-card) {
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
border: 1px solid #e0e0e0;
overflow: hidden;
}
/* 内容区域样式 */
.nova-dialog :deep(.n-card__content) {
padding: 0;
background: white;
}
.nova-dialog-content {
overflow-x: hidden;
}
/* 底部样式 */
.nova-dialog :deep(.n-card__footer) {
padding: 8px 16px 12px;
border-top: 1px solid #f0f0f0;
background: #fafafa;
}
.nova-dialog-footer {
/* 底部操作区域样式已由 n-space 处理 */
}
</style>