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

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

sgFloatDialog 

<template>
  <div :class="$options.name" v-if="visible" :theme="theme" :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="toMd()"
            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
      ref="sgDragMove"
      :data="dragMoveDoms"
      :cursor="{
        grab: 'default',
        grabbing: 'default',
      }"
      nearPadding="10"
      :disabled="changeSizeStatus"
      @dragging="draggingMove"
      mousemoveNearSide
    />

    <!-- 拖拽改变窗体尺寸 -->
    <sgDragSize
      v-if="resizeable"
      :disabled="size === `lg`"
      @dragStart="changeSizeStatus = true"
      @dragging="draggingSize"
      @dragEnd="changeSizeStatus = 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 {
      form: {},
      theme: ``, //主题
      //----------------------------------------
      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全屏最大化 lgl左侧最大化 lgr右侧最大化 md普通 mn最小 rb右下角
      changeSizeStatus: 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(`theme`, this);
          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, //拖拽的时候,整个上传列表一起跟随移动
              },
            ];
            this.style_bk = {
              right: `${this.defaultRight}px`,
              bottom: `${this.defaultBottom}px`,
              width: `${this.defaultWidth}px`,
              height: `${this.defaultHeight}px`,
            };
          });
        }
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },

    resizeable: {
      handler(newValue, oldValue) {
        newValue === undefined || (this.resizeable = newValue === "" || newValue);
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
    style: {
      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.size = null;
      this.style_bk && (this.style = JSON.parse(JSON.stringify(this.style_bk)));
    },
    bkStyle() {
      this.style && (this.style_bk = JSON.parse(JSON.stringify(this.style)));
    },
    fromLgToMd(e) {
      let { x, y } = e.$event;
      let style = JSON.parse(JSON.stringify(this.style_bk));
      let leftPointDis = (x / innerWidth) * parseFloat(style.width); //计算鼠标到左上角定点的距离
      this.$refs.sgDragMove.setOffset({ x: leftPointDis });
    },
    draggingMove(e) {
      let { x, y } = e.$event;
      let dis = 5; //吸附屏幕边缘
      if (y <= dis) {
        this.size = `lg`; // 拖拽到浏览器顶部
      } else if (x <= dis) {
        this.size = `lgl`; // 拖拽到浏览器左侧
      } else if (x >= innerWidth - dis) {
        this.size = `lgr`; // 拖拽到浏览器右侧
      } else {
        // 拖拽回到浏览器中间
        if ((this.size || "").includes(`lg`)) {
          this.fromLgToMd(e);
        } else {
          this.style = e.moveDomStyle;
          this.bkStyle();
        }
        this.size = null;
      }
    },
    draggingSize({ style }) {
      this.style = style;
      this.bkStyle();
      this.size = null;
    },
    dblclickHeader(d) {
      switch (this.size) {
        case "lg":
          this.size = "md";
          break;
        case "mn":
          this.size = "md";
          break;
        case "md":
        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);
  font-size: 14px;
  // 主题----------------------------------------
  // 圆角主题
  &[theme="radius"] {
    border-radius: 8px;
    box-shadow: 0 0 35px 0 rgba(0, 0, 0, 0.08), 0px -5px 0 0 #409eff;
    &::before {
      content: none;
    }
    .header {
      margin-top: revert;
    }
  }
  // 上传主题
  &[theme="upload"] {
    border-radius: 8px;
    border: 1px solid #172d4533;
    box-shadow: 0 4px 6px 0 #172d4533;
    &::before {
      content: none;
    }
    .header {
      margin-top: revert;
    }
  }

  // 右上角按钮----------------------------------------
  // 最大化
  &[size="lg"] {
    left: 0 !important;
    top: 0 !important;
    width: 100vw !important;
    height: 100vh !important;
  }
  // 最大化(左侧)
  &[size="lgl"] {
    left: 0 !important;
    top: 0 !important;
    width: calc(100vw / 2) !important;
    height: 100vh !important;
  }
  // 最大化(右侧)
  &[size="lgr"] {
    left: calc(100vw / 2) !important;
    top: 0 !important;
    width: calc(100vw / 2) !important;
    height: 100vh !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: {
        // theme: `radius`,//主题
        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


http://www.kler.cn/a/593994.html

相关文章:

  • AdaWaveNet:用于时间序列分析的自适应小波网络
  • 高并发编程有哪些规范?
  • 群晖中的docker设置总是不生效,尤其代理
  • 天梯赛 L2-011 玩转二叉树
  • 【MyDB】7-客户端服务端通信之3-Client的实现
  • AI日报 - 2025年3月21日
  • 使用FastAPI为知识库问答系统前端提供后端功能接口
  • 期货和期权的区别,通俗易懂!
  • ccf3401矩阵重塑(其一)
  • deepseek使用记录24——小灵
  • Spring 事务注解原理
  • [Xilinx]工具篇_Vivado自动安装
  • 计算机网络快速入门
  • 【C#知识点详解】ExcelDataReader介绍
  • 记一次性能调优-20250320
  • 【嵌入式硬件】 天线与距离问题
  • JVM常用概念之压缩引用
  • C语言的setjmp和longjmp:可以作异常处理
  • 【数据分享】2000—2024年我国乡镇的逐月归一化植被指数(NDVI)数据(Shp/Excel格式)
  • 微信小程序面试内容整理-请求优化