当前位置: 首页 > news >正文

【sgFloatDialog】自定义组件:浮动弹窗,支持修改尺寸、拖拽位置、最大化、还原、最小化、复位

sgFloatDialog 

<template>
  <div :class="$options.name" v-if="visible" :size="size" :style="style">
    <!-- 托盘头部 -->
    <div class="header" ref="header" @dblclick.stop.prevent="dblclickHeader">
      <div class="left">
        <div class="title">
          <i :class="titleIcon" style="margin-right: 5px" />
          <span>{{ title }}</span>
        </div>
      </div>
      <div class="right" @mousedown.stop>
        <!-- 控制托盘的图标按钮 -->
        <div class="tray-btns">
          <div
            class="icon-btn"
            v-if="show_rb_btn"
            v-show="![`rb`, `mn`, `lg`].includes(size)"
            @dblclick.stop
            @click.stop="size = `rb`"
            title="回到原来的位置"
          >
            <i class="el-icon-bottom-right"></i>
          </div>
          <div
            class="icon-btn"
            v-if="show_mn_btn"
            v-show="size !== `mn`"
            @dblclick.stop
            @click.stop="size = `mn`"
            title="最小化"
          >
            <i class="el-icon-minus"></i>
          </div>
          <div
            class="icon-btn"
            v-show="[`mn`, `lg`].includes(size)"
            @dblclick.stop
            @click.stop="size = `md`"
            title="还原"
          >
            <i :class="size === `lg` ? `el-icon-copy-document` : `el-icon-d-caret`"></i>
          </div>
          <div
            class="icon-btn"
            v-show="size !== `lg`"
            @dblclick.stop
            @click.stop="size = `lg`"
            title="全屏"
          >
            <i class="el-icon-full-screen"></i>
          </div>
          <div class="icon-btn" @dblclick.stop @click.stop="close">
            <i class="el-icon-close"></i>
          </div>
        </div>
      </div>
    </div>
    <div class="body"><slot /></div>

    <!-- 拖拽移动窗体 -->
    <sgDragMove
      :data="dragMoveDoms"
      :cursor="{
        grab: 'default',
        grabbing: 'default',
      }"
      nearPadding="10"
      :disabled="size === `lg` && disabledDragMove"
      @dragStart="$emit(`dragStart`, dragMoveDoms)"
      @dragging="draggingMove"
      @dragEnd="$emit(`dragEnd`, dragMoveDoms)"
      mousemoveNearSide
    />

    <!-- 拖拽改变窗体尺寸 -->
    <sgDragSize
      v-if="resizeable"
      :disabled="size === `lg`"
      @dragStart="disabledDragMove = true"
      @dragging="draggingSize"
      @dragEnd="disabledDragMove = false"
      :minWidth="minWidth"
      :minHeight="minHeight"
    />
  </div>
</template>
<script>
import sgDragMove from "@/vue/components/admin/sgDragMove";
import sgDragSize from "@/vue/components/admin/sgDragSize";
export default {
  name: "sgFloatDialog",
  components: {
    sgDragMove,
    sgDragSize,
  },
  data() {
    return {
      titleIcon: `el-icon-question`,
      title: `浮动窗口标题`,
      // ----------------------------------------
      defaultRight: 100, //默认出现的位置
      defaultBottom: 20, //默认出现的位置
      defaultWidth: 400, //默认宽度
      defaultHeight: 500, //默认高度
      minWidth: 300, //最小宽度
      minHeight: 50, //最小高度
      // ----------------------------------------
      style_bk: null,
      style: {},
      visible: false,
      show_rb_btn: true, //是否显示回到右下角按钮
      show_mn_btn: true, //是否显示最小化按钮
      size: ``, //lg全屏|md普通|mn最小|rb右下角
      disabledDragMove: false, //屏蔽移动
      dragMoveDoms: [
        /* {
          canDragDom: elementDOM,//可以拖拽的位置元素
          moveDom: elementDOM,//拖拽同步移动的元素
      } */
      ], //可以拖拽移动的物体
      resizeable: true,
    };
  },
  props: ["data", "value"],
  computed: {},
  watch: {
    value: {
      handler(d) {
        this.visible = d;
      },
      deep: true,
      immediate: true,
    },
    visible(d) {
      this.$emit("input", d);
    },

    data: {
      handler(newValue, oldValue) {
        //console.log(`深度监听${this.$options.name}:`, newValue, oldValue);
        if (Object.keys(newValue || {}).length) {
          this.form = JSON.parse(JSON.stringify(newValue));
          this.$g.convertForm2ComponentParam(`titleIcon`, this);
          this.$g.convertForm2ComponentParam(`title`, this);
          this.$g.convertForm2ComponentParam(`defaultRight`, this);
          this.$g.convertForm2ComponentParam(`defaultBottom`, this);
          this.$g.convertForm2ComponentParam(`defaultWidth`, this);
          this.$g.convertForm2ComponentParam(`defaultHeight`, this);
          this.$g.convertForm2ComponentParam(`minWidth`, this);
          this.$g.convertForm2ComponentParam(`minHeight`, this);
          this.$g.convertForm2ComponentParam(`resizeable`, this);

          this.$nextTick(() => {
            this.$el.style.setProperty("--defaultRight", `${this.defaultRight}px`); //js往css传递局部参数
            this.$el.style.setProperty("--defaultBottom", `${this.defaultBottom}px`); //js往css传递局部参数
            this.$el.style.setProperty("--defaultWidth", `${this.defaultWidth}px`); //js往css传递局部参数
            this.$el.style.setProperty("--defaultHeight", `${this.defaultHeight}px`); //js往css传递局部参数
            this.$el.style.setProperty("--minWidth", `${this.minWidth}px`); //js往css传递局部参数
            this.$el.style.setProperty("--minHeight", `${this.minHeight}px`); //js往css传递局部参数
            this.dragMoveDoms = [
              {
                canDragDom: this.$refs.header, //托盘的头部可以拖拽
                moveDom: this.$el, //拖拽的时候,整个上传列表一起跟随移动
              },
            ];
          });
        }
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },

    resizeable: {
      handler(newValue, oldValue) {
        newValue === undefined || (this.resizeable = newValue === "" || newValue);
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
    size: {
      handler(newValue, oldValue) {
        switch (newValue) {
          case "lg":
          case "mn":
            break;
          case "md":
            this.toMd();
            break;
          case "rb":
            break;
        }
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
    style_bk: {
      handler(newValue, oldValue) {
        if (Object.keys(newValue || {}).length) {
          let { width, height, left, top } = newValue;
          width = parseInt(width);
          height = parseInt(height);
          left = parseInt(left);
          top = parseInt(top);

          if (width <= this.minWidth && height <= this.minHeight) {
            this.show_mn_btn = false;
          } else {
            this.show_mn_btn = true;
          }
          let right = innerWidth - left - width;
          let bottom = innerHeight - top - height;
          if (right <= this.defaultRight && bottom < this.defaultBottom) {
            this.show_rb_btn = false;
          } else {
            this.show_rb_btn = true;
          }
        } else {
          this.show_mn_btn = true;
        }
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
  },
  created() {},
  mounted() {},
  destroyed() {},
  methods: {
    toMd() {
      this.style_bk && (this.style = JSON.parse(JSON.stringify(this.style_bk)));
    },
    bkStyle() {
      this.style && (this.style_bk = JSON.parse(JSON.stringify(this.style)));
    },
    draggingMove() {
      let el = this.$el;
      if (el) {
        let rect = el.getBoundingClientRect();
        let { left, top, width, height } = rect;
        this.style = {
          left: `${left}px`,
          top: `${top}px`,
          width: `${width}px`,
          height: `${height}px`,
        };
      }
      this.bkStyle();
      this.size = null;
    },
    draggingSize({ style }) {
      this.disabledDragMove = true;
      this.style = style;
      this.bkStyle();
      this.size = null;
    },
    dblclickHeader(d) {
      switch (this.size) {
        case "lg":
          this.size = "md";
          break;
        case "md":
          this.size = "lg";
          break;
        case "mn":
          this.size = "md";
          break;
        default:
          this.size = "lg";
          break;
      }
    },
    // 关闭托盘
    close() {
      this.visible = false;
      this.$emit(`close`, this.style);
    },
  },
};
</script>
<style lang="scss" scoped>
.sgFloatDialog {
  $headerHeight: 40px; //头部高度
  $defaultRight: var(--defaultRight); //托盘默认距离右下角的位置
  $defaultBottom: var(--defaultBottom); //托盘默认距离右下角的位置
  $defaultWidth: var(--defaultWidth); //托盘默认宽度
  $defaultHeight: var(--defaultHeight); //托盘默认高度
  $minWidth: var(--minWidth); //托盘最小宽度
  $minHeight: var(--minHeight); //托盘最小高度

  // ----------------------------------------
  transition: none;
  position: fixed;
  z-index: 2001; //根据情况自己拿捏(太大了会遮住element的其他弹窗组件),v-loading默认是2000的z-index
  user-select: none;
  margin: 0;
  padding: 0;
  right: 100px;
  bottom: 20px;
  width: $defaultWidth;
  height: $defaultHeight;
  background-color: white;
  min-width: $minWidth;
  min-height: $minHeight;
  box-shadow: 0 0 35px 0 rgba(0, 0, 0, 0.08);
  //   border-radius:0 0 8px 8px;
  overflow: hidden;
  font-size: 14px;
  //   border: 1px solid #172d4533;
  //   box-shadow: 0 4px 6px 0 #172d4533;

  // 最大化
  &[size="lg"] {
    left: 0 !important;
    top: 0 !important;
    width: 100vw !important;
    height: 100vh !important;
    .upload-file-list {
      max-height: calc(100vh - 60px) !important;
    }
  }

  // 还原
  &[size="md"] {
  }

  // 最小化
  &[size="mn"] {
    left: revert !important;
    top: revert !important;
    right: $defaultRight !important;
    bottom: $defaultBottom !important;
    width: $minWidth !important;
    height: $minHeight !important;
  }

  // 右下角
  &[size="rb"] {
    left: revert !important;
    top: revert !important;
    right: $defaultRight !important;
    bottom: $defaultBottom !important;
  }
  &::before {
    content: "";
    width: 100%;
    height: 5px;
    /*从左往右线性渐变背景*/
    background: linear-gradient(to right, #409eff, #f56c6c);
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
  }

  .header {
    margin-top: 5px;
    flex-shrink: 0;
    font-size: 16px;
    font-weight: bold;
    width: 100%;
    height: $headerHeight;
    box-sizing: border-box;
    padding: 10px 20px;
    /*从上往下线性渐变背景*/
    background: linear-gradient(#409eff11, white);
    color: #409eff;
    display: flex;
    justify-content: space-between;
    align-items: center;
    &:hover {
      background: linear-gradient(#409eff22, white);
    }

    .left {
      display: flex;
      align-items: center;
      flex-grow: 1;

      .title {
        display: flex;
        align-items: center;
        flex-wrap: nowrap;
      }
      .icon-btns {
        display: flex;
        align-items: center;
        flex-wrap: nowrap;

        .icon-btn {
          cursor: pointer;
          margin-right: 5px;
          &:last-of-type {
            margin-right: 0;
          }
          i {
            pointer-events: none;
          }

          &:hover {
            opacity: 0.618;
          }
        }
      }
    }

    .right {
      display: flex;
      align-items: center;
      justify-content: flex-end;
      flex-shrink: 0;
      pointer-events: auto;

      .icon-btn {
        margin-left: 10px;
        cursor: pointer;

        i {
          pointer-events: none;
        }

        &:hover {
          opacity: 0.618;
        }
        &:first-of-type {
          margin-left: 0;
        }
      }

      .file-btns {
        margin-left: 10px;
        display: flex;
        flex-wrap: nowrap;
        justify-content: flex-end;
        box-sizing: border-box;
        padding: 0 10px;
        border-right: 1px solid #eee;
      }
      .tray-btns {
        margin-left: 10px;
        display: flex;
        flex-wrap: nowrap;
        justify-content: flex-end;
      }
    }
  }

  .body {
    box-sizing: border-box;
    padding: 20px;
    padding-top: 0;
    display: flex;
    flex-wrap: nowrap;
    width: 100%;
    height: calc(100% - #{$headerHeight});
  }
}
</style>

demo

<template>
  <!-- 指导帮助悬浮弹窗 -->
  <sgFloatDialog
    :data="data_sgFloatDialog"
    v-model="show_sgFloatDialog"
    v-if="show_sgFloatDialog"
    @close="close"
  >
    <div
      style="
        width: 100%;
        height: 100%;
        overflow-y: auto;
        font-size: 16px;
        text-indent: 32px;
        line-height: 1.6;
      "
    >
      <p>
        我一路向北,离开有你的季节,你说你好累,已无法再爱上谁。风在山路吹,过往的画面全都是不对,细数惭愧,我伤你几回。
      </p>
      <p>
        我想我是太过依赖,在挂电话的刚才,坚持学单纯的小孩,静静看守这份爱,知道不能太依赖,怕你会把我宠坏,你的香味一直徘徊,我舍不得离开。
      </p>
      <p>
        缓缓飘落的枫叶像思念,为何挽回要赶在冬天来之前,爱你穿越时间,两行来自秋末的眼泪,让爱渗透了地面我要的只是你在我身边。
      </p>
      <p>
        我知道你我都没有错,只是忘了怎么退后,信誓旦旦给的承诺,全被时间扑了空。我知道我们都没有错,只是放手会比较好过,最美的爱情回忆里待续。
      </p>
    </div>
  </sgFloatDialog>
</template>
<script>
import sgFloatDialog from "@/vue/components/admin/sgFloatDialog";

export default {
  components: { sgFloatDialog },
  data() {
    return {
      data_sgFloatDialog: {
        titleIcon: `el-icon-info`,
        title: `标题`,
        defaultRight: 10,
        defaultBottom: 10,
        defaultWidth: 400,
        defaultHeight: 600,
      },
      show_sgFloatDialog: true,
    };
  },
  methods: {
    close(d) {
      // console.log(``, d);
    },
  },
};
</script>

依赖

【sgDragMove】自定义组件:自定义拖拽组件,仅支持拖拽、设置吸附屏幕边界距离。_自定义拖拽位置-CSDN博客文章浏览阅读245次。sgDragMove是一个支持拖拽元素并能吸附到屏幕边界的Vue.js组件。它允许用户自定义吸附距离,可以设置拖拽行为是否禁用,以及停靠边界距离。组件在拖拽过程中提供了半透明效果,并能在不同阶段监听和处理鼠标事件以实现拖拽、吸附和停靠功能。 https://blog.csdn.net/qq_37860634/article/details/131721634【sgDragSize】自定义组件:自定义拖拽修改DIV尺寸组件,适用于窗体大小调整_div拖拽调整大小-CSDN博客文章浏览阅读505次。核心原理就是在四条边、四个顶点加上透明的div,给不同方向提供按下移动鼠标监听 ,对应计算宽度高度、坐标变化。_div拖拽调整大小 https://blog.csdn.net/qq_37860634/article/details/132347222

相关文章:

  • Vue3 在组件中判断事件是否注册
  • js原型链与自动装箱机制
  • 从OSI七层网络模型角度了解CAN通信协议
  • 关于金融开发领域的一些专业知识总结
  • jmeter接口测试[-面试篇-]
  • 【YOLOv8改进 - C2f融合】C2f融合SCConv :即插即用的空间和通道重建卷积
  • 我的uniapp自定义模板
  • 基于SpringBoot + Vue 的药店药品信息管理系统
  • Yolo v4 (Darknet) Mac M2 安装与运行
  • kmp算法的实现
  • 测试专项3:算法测试基础理论速查手册
  • Spring Boot 整合 Apache Flink 教程
  • 二. JAVA数据类型与变量
  • 软考中级-软件设计师 准备
  • OpenWrt开发第4篇:设置开发板的IP-基于Raspberry Pi 4B开发板
  • 2025-03-20 学习记录--C/C++-C 库函数 - toupper()、tolower()、 isspace()
  • Python(冒泡排序、选择排序、插入法排序、快速排序,算法稳定性)
  • 双碳战略下的电能质量革命:解码电力系统的健康密码
  • 服务的拆分数据的迁移
  • Springboot项目搭建(9)-分页与文件上传
  • 以色列在加沙发起新一轮强攻,同步与哈马斯展开“无条件谈判”
  • 天问二号探测器顺利转入发射区
  • 机器人为啥热衷“搞体育”,经济日报:是向加速融入日常生活发起的冲锋
  • 海昏侯博物馆展览上新,“西汉帝陵文化展”将持续展出3个月
  • 武大校长:人工智能不存在“过度使用”,武大不会缩减文科
  • 涉案资金超2亿元 “健康投资”骗局,专挑老年人下手