引言
在 Vue 项目中,Element UI 的 el-dialog
是一个非常实用的对话框组件。但在实际开发中,我们经常会遇到需要重复设置对话框属性、处理相同逻辑的情况。通过二次封装,我们可以创建一个更灵活、更易用的对话框组件,提高开发效率并保持代码一致性。
为什么需要二次封装?
- 减少重复代码:多个对话框可能需要相同的逻辑(如关闭确认、表单重置等)
- 统一风格:确保所有对话框的视觉和交互行为一致
- 简化使用:通过默认值和封装方法,减少每次使用时需要编写的代码
- 增强功能:添加常用功能如加载状态、国际化支持等
封装思路
我们将创建一个 Dialog
组件,它封装了那些扩展?:
- 封装了
el-dialog
的常用属性和事件 - 动态按钮大小,按钮标题动态展示,按钮的type类型
- 弹窗的动态宽度
- 是否显示关闭,取消按钮
- 添加了确认关闭逻辑
- 支持插槽内容(内容根据项目随意调整)
- 提供统一的关闭方法
- 批量按钮的动态添加
- 窗口的响应式
代码实现
1. 基础封装组件 (Dialog.vue)
<template>
<div>
<el-dialog
class="cust-dialog"
:model-value="show"
:title="title"
:width="dialogWidth"
:top="top + 'px'"
@close="close"
:draggable="true"
:show-close="showClose"
:modal-class="'dialog-fade'"
>
<div
class="dialog-body"
>
<slot></slot>
</div>
<template #footer>
<span class="dialog-footer">
<slot name="footer">
<!-- 默认footer内容 -->
<el-button type="danger" @click="close" v-if="showCancel"
>取消</el-button
>
<el-button
v-for="item in buttons"
@click="item.click"
:type="item.type || primary"
:size="item.size"
>
{{ item.text }}
</el-button>
</slot>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { computed, watch, onMounted, onUnmounted, ref } from "vue";
const props = defineProps<{
show: { type: Boolean; default: false };
title: { type: String; default: "提示" };
width: { type: String; default: "30%" };
top: { type: Number; default: 50 };
padding: { type: Number; default: 15 };
showClose: { type: Boolean; default: true };
showCancel: { type: Boolean; default: true };
buttons: {
type: Array<{
text: string;
click: () => void;
type?: "primary" | "success" | "warning" | "danger" | "info";
size?: "large" | "small" | "default";
}>;
default: () => [];
};
}>();
const dialogWidth = ref(props.width);
// 监听窗口大小变化,根据窗口宽度动态设置dialog宽度
onMounted(() => {
const handleResize = () => {
if (window.innerWidth <= 768) {
dialogWidth.value = "50%";
} else {
dialogWidth.value = props.width;
}
};
window.addEventListener("resize", handleResize);
});
onUnmounted(() => {
window.removeEventListener("resize", handleResize);
});
const emit = defineEmits(["close"]);
const close = () => {
emit("close");
};
</script>
<style lang="scss">
@keyframes fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.dialog-fade {
animation: fade 0.3s;
}
.cust-dialog {
margin: 30px auto 10px !important;
.el-dialog__body {
padding: 0px;
}
.dialog-body {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
min-height: 80px;
overflow: auto;
/* 自定义滚动条 */
&::-webkit-scrollbar {
width: 3px;
height: 8px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
transition: background 0.3s;
&:hover {
background: #a8a8a8;
}
}
}
.dialog-footer {
text-align: right;
padding: 5px 20px;
}
}
</style>
2. Vue中引入使用示例
<template>
<div>
<Dialog
:show="dialogConfig.show"
:title="dialogConfig.title"
:width="dialogConfig.width"
:top="dialogConfig.top"
:padding="dialogConfig.padding"
:showClose="dialogConfig.showClose"
:showCancel="dialogConfig.showCancel"
:buttons="dialogConfig.buttons"
@close="dialogConfig.show = false"
>
<el-form :model="form" label-width="auto" style="max-width: 600px">
<el-form-item label="Activity name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="Activity zone">
<el-select
v-model="form.region"
placeholder="please select your zone"
>
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="Activity time">
<el-col :span="11">
<el-date-picker
v-model="form.date1"
type="date"
placeholder="Pick a date"
style="width: 100%"
/>
</el-col>
<el-col :span="2" class="text-center">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
<el-time-picker
v-model="form.date2"
placeholder="Pick a time"
style="width: 100%"
/>
</el-col>
</el-form-item>
<el-form-item label="Instant delivery">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="Activity type">
<el-checkbox-group v-model="form.type">
<el-checkbox value="Online activities" name="type">
Online activities
</el-checkbox>
<el-checkbox value="Promotion activities" name="type">
Promotion activities
</el-checkbox>
<el-checkbox value="Offline activities" name="type">
Offline activities
</el-checkbox>
<el-checkbox value="Simple brand exposure" name="type">
Simple brand exposure
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="Resources">
<el-radio-group v-model="form.resource">
<el-radio value="Sponsor">Sponsor</el-radio>
<el-radio value="Venue">Venue</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="Activity form">
<el-input v-model="form.desc" type="textarea" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">Create</el-button>
<el-button>Cancel</el-button>
</el-form-item>
</el-form>
</Dialog>
<el-button type="primary" @click="showDialog">点击</el-button>
</div>
</template>
<script setup lang="ts">
import Dialog from "@/components/Dialog.vue";
import { ref, reactive } from "vue";
const dialogConfig = ref({
show: false, // 控制弹窗显示
title: "测试弹窗", // 标题
width: "80%", // 宽度
top: 50, // 距离顶部高度
padding: 15, // 内容内边距
showClose: true, // 是否显示关闭按钮
showCancel: true, // 是否显示取消按钮
buttons: [
{
text: "确定",
click: () => console.log("确定"),
type: "success",
size: "default",
},
],
});
const showDialog = () => {
dialogConfig.value.show = true;
};
const form = reactive({
name: "",
region: "",
date1: "",
date2: "",
delivery: false,
type: [],
resource: "",
desc: "",
});
const onSubmit = () => {
console.log("submit!");
};
</script>
<style lang="scss" scoped></style>
效果:
封装后的优势
统一配置:所有对话框共享相同的配置项,如关闭行为、按钮文本等
简化使用:只需关注对话框内容,无需重复设置相同属性
增强功能:
- 内置加载状态
- 统一的确认/取消逻辑
- 可选的关闭前确认
更好的可维护性:所有对话框逻辑集中在一个组件中
灵活性:
- 保留
el-dialog
的所有属性和事件(通过$attrs
透传) - 支持插槽自定义内容
- 支持自定义底部按钮
- 保留
进阶优化建议
- 国际化支持:将按钮文本等可配置项提取到语言包中
- 主题定制:通过 CSS 变量或 props 允许自定义样式
- 动画效果:添加自定义进入/离开动画
- 表单验证集成:如果主要用于表单,可以集成表单验证逻辑
- 响应式宽度:根据内容自动调整宽度
总结
通过对 el-dialog
的二次封装,我们创建了一个更强大、更易用的对话框组件。这不仅减少了重复代码,还提高了开发效率和代码一致性。在实际项目中,可以根据团队需求进一步扩展这个基础组件,添加更多实用功能。
希望这个封装方案能为你的 Vue 项目开发带来便利!如果你有任何改进建议或使用中的问题,欢迎在评论区交流。