二次封装 el-dialog 组件:打造更灵活的对话框解决方案

发布于:2025-05-14 ⋅ 阅读:(12) ⋅ 点赞:(0)


引言

在 Vue 项目中,Element UI 的 el-dialog 是一个非常实用的对话框组件。但在实际开发中,我们经常会遇到需要重复设置对话框属性、处理相同逻辑的情况。通过二次封装,我们可以创建一个更灵活、更易用的对话框组件,提高开发效率并保持代码一致性。

为什么需要二次封装?

  1. 减少重复代码:多个对话框可能需要相同的逻辑(如关闭确认、表单重置等)
  2. 统一风格:确保所有对话框的视觉和交互行为一致
  3. 简化使用:通过默认值和封装方法,减少每次使用时需要编写的代码
  4. 增强功能:添加常用功能如加载状态、国际化支持等

封装思路

我们将创建一个 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>

效果:
在这里插入图片描述

封装后的优势

  1. 统一配置:所有对话框共享相同的配置项,如关闭行为、按钮文本等

  2. 简化使用:只需关注对话框内容,无需重复设置相同属性

  3. 增强功能

    • 内置加载状态
    • 统一的确认/取消逻辑
    • 可选的关闭前确认
  4. 更好的可维护性:所有对话框逻辑集中在一个组件中

  5. 灵活性

    • 保留 el-dialog 的所有属性和事件(通过 $attrs 透传)
    • 支持插槽自定义内容
    • 支持自定义底部按钮

进阶优化建议

  1. 国际化支持:将按钮文本等可配置项提取到语言包中
  2. 主题定制:通过 CSS 变量或 props 允许自定义样式
  3. 动画效果:添加自定义进入/离开动画
  4. 表单验证集成:如果主要用于表单,可以集成表单验证逻辑
  5. 响应式宽度:根据内容自动调整宽度

总结

通过对 el-dialog 的二次封装,我们创建了一个更强大、更易用的对话框组件。这不仅减少了重复代码,还提高了开发效率和代码一致性。在实际项目中,可以根据团队需求进一步扩展这个基础组件,添加更多实用功能。

希望这个封装方案能为你的 Vue 项目开发带来便利!如果你有任何改进建议或使用中的问题,欢迎在评论区交流。


网站公告

今日签到

点亮在社区的每一天
去签到