封装一个可以最小化和展开的弹窗组件

gl-dialog

大概思路:
在弹窗组件内部引入gl-dialog-collapse,这个组件主要用于存储已经被最小化的弹窗(基础数据)
弹窗内部的数据如何在父组件拿到是通过作用域插槽来实现的
gl-dialog接收一个tempData这个数据会在内部被记录下来,然后通过插槽的形式传递给父组件,供父组件使用
在这里插入图片描述

<template>
  <div class="gl-dialog" ref="dialogRef">
    <el-dialog
      :close-on-click-modal="false"
      v-bind="$attrs"
      :visible.sync="selfVisible"
      :show-close="false"
    >
      <div class="dialog-header" slot="title">
        <div class="left-title">{{ currentData.dialogTitle }}</div>
        <div class="right-icon">
          <i
            title="缩小"
            class="iconfont icon-segi-icon-minus"
            style="font-size: 14px"
            @click="toCollapse"
          ></i>
          <i
            title="关闭"
            class="iconfont icon-Close"
            style="font-size: 14px;font-weight: bold;"
            @click="closeDialog"
          ></i>
        </div>
      </div>
      <slot :tempData="currentData"></slot>
      <footer>
        <slot name="footer" :tempData="currentData" />
      </footer>
    </el-dialog>
    <gl-dialog-collapse :dialogList="dialogList"></gl-dialog-collapse>
  </div>
</template>

<script>
import _ from "lodash";
export default {
  name: "gl-dialog",
  props: {
    visible: {
      type: Boolean,
      default: () => false,
    },
    title: String,
    type: {
      type: String,
      default: () => "default",
    },
    tempData: Object,
  },
  data() {
    return {
      ddd: "okok",
      dialogList: [],
      currentData: {},
      count: 0,
      isExpand: false,
      // dialogId: "",
    };
  },
  computed: {
    dialogId() {
      return this.tempData.dialogTitle + this.count;
    },
    selfVisible: {
      get() {
        return this.visible;
      },
      set(value) {
        this.$emit("update:visible", value);
      },
    },
  },
  watch: {
    visible(N) {
      if (N) {
        if (!this.isExpand) {
          this.currentData = _.cloneDeep(this.tempData);
          this.currentData.dialogTitle = this.title;
          this.currentData.dialogId = this.dialogId;
        } else {
          this.count++;
        }
      }
    },
  },
  methods: {
    toCollapse() {
      const targetIndex = this.dialogList.findIndex((item) => {
        const { dialogId } = this.currentData;
        return dialogId == item.dialogId;
      });
      const isExist = targetIndex >= 0;
      if (!isExist) {
        this.dialogList.push({
          type: this.type,
          title: this.currentData.dialogTitle,
          dialogId: this.dialogId,
          tempData: this.currentData,
          expandCallBack: (tempData) => {
            this.isExpand = true;
            this.currentData = tempData;
            this.currentData.dialogId = tempData.dialogId;
            this.selfVisible = true;
          },
        });
        this.count++;
      }
      this.isExpand = false;
      this.selfVisible = false;
    },
    closeDialog() {
      this.isExpand = false;
      this.selfVisible = false;
      if (!this.dialogList.length) return;
      const targetIndex = this.dialogList.findIndex((item) => {
        const dialogId = this.isExpand
          ? this.currentData.dialogId
          : this.dialogId;
        return dialogId == item.dialogId;
      });
      if (targetIndex >= 0) {
        this.dialogList.splice(targetIndex, 1);
      }
    },
  },
};
</script>

<style lang="scss" scoped>
.dialog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
// ::v-deep .dialog-footer {
//   text-align: right!important;
// }
.iconfont {
  cursor: pointer;
}
footer {
  text-align: right;
}
</style>

gl-dialog-collapse.vue

<template>
  <div class="gl-dialog-collapse">
    <div class="collapse-item" v-for="(item, index) in dialogList" :key="index">
      <div class="title">{{ item.title }}</div>
      <div class="right-icons">
        <i
          title="放大"
          class="iconfont icon-icf_full_screen_arrow"
          @click="toExpand(item)"
        ></i>
        <i title="关闭" class="iconfont icon-Close" @click="closeDialog"></i>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "gl-dialog-collapse",
  props: {
    dialogList: {
      type: Array,
      default: [],
    },
  },
  methods: {
    toExpand(item) {
      const { expandCallBack, tempData } = item;
      expandCallBack(tempData);
      this.closeDialog(item);
    },
    closeDialog(item) {
      const { dialogId } = item;
      const targetIndex = this.dialogList.findIndex(
        (item) => item.dialogId == dialogId
      );
      this.dialogList.splice(targetIndex, 1);
    },
  },
};
</script>

<style lang="scss" scoped>
.gl-dialog-collapse {
  display: flex;
  column-gap: 5px;
  position: fixed;
  left: 201px;
  bottom: 0px;
}
.collapse-item {
  padding: 5px 10px;
  display: flex;
  align-items: center;
  column-gap: 20px;
  border: 1px solid #ccc;
  background-color: #fff;
  .title {
    font-size: 14px;
  }
}
.right-icons {
  i {
    cursor: pointer;
    font-size: 16px;
  }
}
</style>

实现效果:
在这里插入图片描述
在这里插入图片描述
每个最小化的弹窗内部数据都是独立的,因为gl-dialog-collapse内部维护了一个已经被折叠的弹窗数组。
内部的数据结构:

{
          type: this.type,
          title: this.currentData.dialogTitle,
          dialogId: this.dialogId,
          tempData: this.currentData,
          expandCallBack: (tempData) => {
            this.isExpand = true;
            this.currentData = tempData;
            this.currentData.dialogId = tempData.dialogId;
            this.selfVisible = true;
          },
        }

dialogId用于记录唯一弹窗,方便回显数据,以及关闭目标弹窗

反思:当时考虑用cloneNode来实现弹窗的复制,但是考虑到vue里面是通过数据来驱动视图,能够成功复制弹窗,但是里面的交互会失效,所以感觉这种方案会很复杂,所以放弃。中途参考过layui弹窗最小化的实现方式,发现是对节点进行克隆,所以每最小化一个弹窗就会多产生一个节点。

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个基本的弹窗组件封装代码。代码中使用的是Element UI库中的Dialog组件,你可以根据自己的需求进行修改和定制。 ``` <template> <el-dialog :title="title" :visible.sync="visible" :modal="modal" :width="width" :fullscreen="fullscreen" :lock-scroll="lockScroll" :custom-class="customClass" :show-close="showClose" :before-close="beforeClose" > <slot></slot> </el-dialog> </template> <script> export default { name: 'MyDialog', props: { title: { // 弹窗标题 type: String, default: '' }, visible: { // 是否显示弹窗 type: Boolean, default: false }, modal: { // 是否为模态弹窗 type: Boolean, default: true }, width: { // 弹窗宽度 type: String, default: '50%' }, fullscreen: { // 是否全屏显示 type: Boolean, default: false }, lockScroll: { // 是否锁定滚动条 type: Boolean, default: true }, customClass: { // 自定义类名 type: String, default: '' }, showClose: { // 是否显示关闭按钮 type: Boolean, default: true }, beforeClose: { // 关闭前的回调函数 type: Function, default: () => {} } } } </script> ``` 这是一个非常基础的弹窗组件,你可以根据自己的需求进行扩展和修改。在使用时,你需要在父组件中引入该组件,并使用v-model绑定visible属性来控制弹窗的显示和隐藏状态。 ``` <template> <div> <el-button @click="visible = true">打开弹窗</el-button> <my-dialog v-model="visible" title="弹窗标题"> <p>这是弹窗内容</p> </my-dialog> </div> </template> <script> import MyDialog from './MyDialog.vue' export default { components: { MyDialog }, data() { return { visible: false } } } </script> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值