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

【sgSpliter】自定义组件:可调整宽度、高度、折叠的分割线

特性:

  1. 允许设置显示折叠按钮
  2. 允许设置折叠线按钮位置
  3. 允许设置当拖拽区域到0,再点击箭头展开的默认宽度
  4. 允许设置当拖拽宽度小于此宽度,自动折叠到0
  5. 允许设置指定最小宽度
  6. 允许设置指定最大宽度
  7. 允许设置按钮风格:白色背景default、蓝色背景blue
  8. 允许设置分隔条大小,默认 2px
  9. 允许设置指定 是否可调整大小,会影响相邻
  10. 允许设置外部触发显示折叠按钮
  11. 允许设置禁止双击折叠线

sgSpliter.vue 

<template>
  <!-- 注意:
  父组件position必须是relative、absolute或fixed,
  不建议直接在绑定:data后面用"{属性}",
  建议单独在script中声明data,避免拖拽过程重复调用 
  -->
  <div
    :class="$options.name"
    :placement="placement"
    @mousedown="__addWindowEvents"
    @dblclick="dblclickSpliterLine"
    :unresizable="!resizable"
    :mouseoverShowArrowBtn="mouseoverShowArrowBtn"
  >
    <div
      v-if="showArrowBtn"
      class="arrow-btn"
      @click="clickArrowBtn"
      @mousedown.stop
      :styleType="arrowBtnStyleType"
      :collapse="collapse"
    >
      <!-- 箭头在父组件的最左侧 -->
      <template v-if="placement === `left`">
        <i class="el-icon-arrow-left" v-if="collapse" />
        <i class="el-icon-arrow-right" v-else />
      </template>

      <!-- 箭头在父组件的最右侧 -->
      <template v-if="placement === `right`">
        <i class="el-icon-arrow-right" v-if="collapse" />
        <i class="el-icon-arrow-left" v-else />
      </template>

      <!-- 箭头在父组件的最上侧 -->
      <template v-if="placement === `top`">
        <i class="el-icon-arrow-up" v-if="collapse" />
        <i class="el-icon-arrow-down" v-else />
      </template>

      <!-- 箭头在父组件的最下侧 -->
      <template v-if="placement === `bottom`">
        <i class="el-icon-arrow-down" v-if="collapse" />
        <i class="el-icon-arrow-up" v-else />
      </template>
    </div>
  </div>
</template>
<script>
export default {
  name: "sgSpliter",
  components: {},
  data() {
    return {
      form: {},
      collapse: false,
      showArrowBtn: true,//显示折叠按钮
      placement: `right`,//折叠线按钮位置
      parent: null,
      defaultSize: 200, //当拖拽区域到0,再点击箭头展开的默认宽度
      nearEdgeSize: 5, //当拖拽宽度小于此宽度,自动折叠到0
      minSize: null, //可选,指定 最小宽度
      maxSize: null, //可选,指定 最大宽度
      size: null,
      size_bk: null,
      arrowBtnStyleType: `default`, //按钮风格:白色背景default、蓝色背景blue
      splitBarSize: 1, //可选,分隔条大小,默认 2px
      resizable: true, //可选,指定 是否可调整大小,会影响相邻
      mouseoverShowArrowBtn: false, //外部触发显示折叠按钮
      disabledDblclickSpliterLine: false, //禁止双击折叠线
    };
  },
  props: ["data"],
  computed: {},
  watch: {
    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(`showArrowBtn`, this);
          this.$g.convertForm2ComponentParam(`defaultSize`, this);
          this.$g.convertForm2ComponentParam(`nearEdgeSize`, this);
          this.$g.convertForm2ComponentParam(`minSize`, this);
          this.$g.convertForm2ComponentParam(`maxSize`, this);
          this.$g.convertForm2ComponentParam(`placement`, this);
          this.$g.convertForm2ComponentParam(`parent`, this);
          this.$g.convertForm2ComponentParam(`arrowBtnStyleType`, this);
          this.$g.convertForm2ComponentParam(`splitBarSize`, this);
          this.$g.convertForm2ComponentParam(`resizable`, this);
          this.$g.convertForm2ComponentParam(`mouseoverShowArrowBtn`, this);
          this.$g.convertForm2ComponentParam(`disabledDblclickSpliterLine`, this);
          this.form.hasOwnProperty("collapse") &&
            this.collapseSpliter({ collapse: this.form.collapse }); //允许外部控制默认折叠或展开
          this.$nextTick(() => {
            this.$el.style.setProperty(`--splitBarSize`, `${this.splitBarSize}px`); //js往css传递局部参数
          });

          // 不要在这里初始化size_bk,由于拖拽过程会重复触发这里的代码执行
        }
      },
      deep: true, //深度监听
      immediate: true, //立即执行
    },
    size(size) {
      size <= this.nearEdgeSize && (size = 0);
      this.collapse = size === 0;
      this.$emit(`sizeChange`, { size });
    },
    collapse(collapse) {
      this.$emit(`collapseChange`, { collapse });
    },
  },
  mounted() {
    this.$nextTick(() => {
      this.parent || (this.parent = this.$el.parentNode);
      if (this.parent) {
        let rect = this.parent.getBoundingClientRect();
        switch (this.placement) {
          case `left`: // 竖线在父组件的最左侧
          case `right`: // 竖线在父组件的最右侧
            this.size_bk = rect.width;
            break;
          case `top`: // 竖线在父组件的最上侧
          case `bottom`: // 竖线在父组件的最下侧
            this.size_bk = rect.height;
            break;
          default:
        }
      }
    });
  },

  beforeDestroy() {
    this.__removeWindowEvents();
  },
  methods: {
    //size发生变化的时候就做缓动效果
    changeTransitionSize() {
      let parent = this.parent;
      if (parent) {
        let attr = `${this.$options.name}-transitionSize`;
        parent.setAttribute(attr, true);
        setTimeout(() => parent.removeAttribute(attr), 200);
      }
    },
    clickArrowBtn($event) {
      this.collapseSpliter();
    },
    dblclickSpliterLine($event) {
      if (this.disabledDblclickSpliterLine || !this.resizable) return;
      this.collapseSpliter();
    },
    collapseSpliter({ collapse } = {}) {
      this.collapse = collapse === undefined ? !this.collapse : collapse;
      let expandSize = this.size_bk > this.nearEdgeSize ? this.size_bk : this.defaultSize;
      this.changeTransitionSize();
      this.size = this.collapse ? 0 : expandSize;
      this.$emit(`sizeChange`, { size: this.size }); //避免上一次size=0的时候,第二次不监听变化
    },
    bkSize(d) {
      this.size_bk = this.size;
    },
    __addWindowEvents() {
      this.__removeWindowEvents();
      addEventListener("mousemove", this.mousemove_window);
      addEventListener("mouseup", this.mouseup_window);
    },
    __removeWindowEvents() {
      removeEventListener("mousemove", this.mousemove_window);
      removeEventListener("mouseup", this.mouseup_window);
    },
    mousemove_window($event) {
      if (!this.resizable) return;
      this.parent || (this.parent = this.$el.parentNode);
      if (this.parent) {
        let { x, y } = $event,
          rect = this.parent.getBoundingClientRect(),
          size;
        switch (this.placement) {
          case `left`: // 竖线在父组件的最左侧
            size = rect.x + rect.width - x;
            break;
          case `right`: // 竖线在父组件的最右侧
            size = x - rect.x;
            break;
          case `top`: // 竖线在父组件的最上侧
            size = rect.y + rect.height - y;
            break;
          case `bottom`: // 竖线在父组件的最下侧
            size = y - rect.y;
            break;
          default:
        }
        this.minSize && size < this.minSize && (size = this.minSize);
        this.maxSize && size > this.maxSize && (size = this.maxSize);
        this.size = size;
        this.bkSize();
      } else {
        this.$message.error(`没有获取到父组件parent!`);
      }
    },
    mouseup_window($event) {
      this.__removeWindowEvents();
    },
  },
};
</script>
<style lang="scss" scoped>
$splitBarSize: var(--splitBarSize); //css获取js传递的参数
.sgSpliter {
  z-index: 1;
  background-color: #efefef;
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  .arrow-btn {
    transition: 0.382s;
    opacity: 0;
    pointer-events: none;
    // transform: translateY(50%); //防止托盘最小高度的时候还冒出一小截
    width: 20px;
    height: 20px;
    display: flex;
    justify-content: center;
    align-items: center;
    color: #409eff;
    background-color: white;
    font-size: 12px;
    position: absolute;
    margin: auto;
    box-sizing: border-box;
    cursor: pointer;
    &:hover {
      filter: brightness(1.1);
    }
    &[styleType="blue"] {
      color: white;
      background-color: #4f6bdf;
    }
    &[collapse] {
      opacity: 1;
      pointer-events: auto;
    }
  }
  // 位置----------------------------------------
  &[placement="left"],
  &[placement="right"] {
    cursor: col-resize;
    width: $splitBarSize;
    height: 100%;
  }
  &[placement="top"],
  &[placement="bottom"] {
    cursor: row-resize;
    width: 100%;
    height: $splitBarSize;
  }
  &[placement="left"] {
    left: 0;
    right: revert;
    .arrow-btn {
      left: revert;
      right: $splitBarSize;
      top: 0;
      bottom: 0;
      border-radius: 8px 0 0 8px;
      padding: 20px 0;
      box-shadow: -5px 0px 10px 0 rgba(0, 0, 0, 0.1);
    }
  }
  &[placement="right"] {
    left: revert;
    right: 0;
    .arrow-btn {
      left: $splitBarSize;
      right: revert;
      top: 0;
      bottom: 0;
      border-radius: 0 8px 8px 0;
      padding: 20px 0;
      box-shadow: 5px 0px 10px 0 rgba(0, 0, 0, 0.1);
    }
  }
  &[placement="top"] {
    top: 0;
    bottom: revert;
    .arrow-btn {
      left: 0;
      right: 0;
      top: revert;
      bottom: $splitBarSize;
      border-radius: 8px 8px 0 0;
      padding: 0 20px;
      box-shadow: 0px -5px 10px 0 rgba(0, 0, 0, 0.1);
    }
  }
  &[placement="bottom"] {
    top: revert;
    bottom: 0;
    .arrow-btn {
      left: 0;
      right: 0;
      top: $splitBarSize;
      bottom: revert;
      border-radius: 0 0 8px 8px;
      padding: 0 20px;
      box-shadow: 0px 5px 10px 0 rgba(0, 0, 0, 0.1);
    }
  }
  // ----------------------------------------
  &[mouseoverShowArrowBtn],
  &:hover {
    background-color: #b3d8ff;
    .arrow-btn {
      opacity: 1;
      pointer-events: auto;
    }
  }
  // 按下拖拽线条后出现的半透明区域
  &::after {
    content: "";
    transition: 0.382s;
    position: absolute;
    background-color: #409eff22;
    opacity: 0;
  }

  $splitOpacityBgExpandSize: 5px; //半透明延伸宽度
  $splitOpacityBgSize: calc(#{$splitOpacityBgExpandSize} * 2 + #{$splitBarSize});
  &[placement="left"],
  &[placement="right"] {
    &::after {
      width: $splitOpacityBgSize;
      height: 100%;
      left: -#{$splitOpacityBgExpandSize};
      top: 0;
    }
  }
  &[placement="top"],
  &[placement="bottom"] {
    &::after {
      width: 100%;
      height: $splitOpacityBgSize;
      left: 0;
      top: -#{$splitOpacityBgExpandSize};
    }
  }
  &:active {
    opacity: 1;
    background-color: #409eff;
    &::after {
      opacity: 1;
    }
  }

  // 禁止拖拽
  &[unresizable] {
    cursor: default;
    background-color: transparent;
    &::after {
      content: none;
    }
  }
}
</style>

<style lang="scss">
[sgSpliter-transitionSize] {
  transition: 0.2s;
}
</style>

demo

<template>
  <div :class="$options.name">
    <div class="left" :style="{ width: `${leftWidth}px` }">
      <sgSpliter :data="{ placement: `right` }" @sizeChange="leftWidth = $event.size" />
    </div>
    <div class="right">
      <div class="top" :style="{ height: `${topHeight}px` }">
        <sgSpliter
          :data="{ placement: `bottom` }"
          @sizeChange="topHeight = $event.size"
        />
      </div>
      <div class="bottom">
        <div class="left">
          <div class="top"></div>
          <div class="bottom" :style="{ height: `${bottomHeight}px` }">
            <sgSpliter
              :data="{ placement: `top` }"
              @sizeChange="bottomHeight = $event.size"
            />
          </div>
        </div>
        <div class="right" :style="{ width: `${bottomWidth}px` }">
          <sgSpliter
            :data="{ placement: `left` }"
            @sizeChange="bottomWidth = $event.size"
          />
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import sgSpliter from "@/vue/components/admin/sgSpliter";
export default {
  name: `demoSpliter`,
  components: { sgSpliter },
  data() {
    return {
      leftWidth: 200,
      topHeight: 200,
      bottomHeight: 200,
      bottomWidth: 200,
    };
  },
};
</script>
<style lang="scss" scoped>
.demoSpliter {
  display: flex;
  & > .left {
    height: 100%;
    flex-shrink: 0;
    position: relative;
    box-sizing: border-box;
    border-right: 1px solid #eee;
  }
  & > .right {
    flex-grow: 1;
    display: flex;
    flex-direction: column;
    & > .top {
      flex-shrink: 0;
      width: 100%;
      position: relative;
      box-sizing: border-box;
      border-bottom: 1px solid #eee;
    }
    & > .bottom {
      flex-grow: 1;
      width: 100%;
      display: flex;
      & > .left {
        flex-grow: 1;
        height: 100%;
        display: flex;
        flex-direction: column;
        & > .top {
          flex-grow: 1;
          width: 100%;
        }
        & > .bottom {
          flex-shrink: 0;
          width: 100%;
          position: relative;
          box-sizing: border-box;
          border-top: 1px solid #eee;
        }
      }
      & > .right {
        flex-shrink: 0;
        height: 100%;
        position: relative;
        box-sizing: border-box;
        border-left: 1px solid #eee;
      }
    }
  }
}
</style>

相关文章:

  • 【技术派部署篇】云服务器部署技术派
  • jeecg启动所需要安装的软件
  • GitHub Desktop 推送报错 Authentication Failed 身份验证失败
  • HarmonyOS 5.0分布式开发深度踩坑指南:从理论到实践的突围之路
  • Java递归练习----猴子偷桃
  • 基于ueditor编辑器的功能开发之增加自定义一键排版功能
  • Java IO 流
  • 【资料分享】瑞芯微RK3576,8核2.2GHz+6T算力NPU工业核心板说明书
  • STM32(基于标准库)
  • 多模态大模型[CLIP/Flamingo/Coca/BLIP]
  • Unity入门
  • 图谱可视化的海洋生物信息查询网站的设计与实现(springboot+ssm+vue)含文档
  • 十八、TCP多线程、多进程并发服务器
  • 气动V型调节开关球阀气源连接尺寸与方式全解析-耀圣
  • 2025 GGS全球游戏峰会前瞻预告:全新版本控制平台Perforce P4、龙智游戏开发及管理解决方案等即将亮相
  • 【家政平台开发(37)】家政平台蜕变记:性能优化与代码重构揭秘
  • Dify添加ollama插件
  • OpenHarmony5.0.2 音频audio适配
  • js中this的指向问题
  • 智能测试用例生成:老旧平台页面查询功能的大模型改造
  • 潍坊网站建设优化排名/晋江友情链接是什么意思
  • 门户网站盈利/搜索网络如何制造
  • 北京比较大的网站建设公司/广东seo点击排名软件哪家好
  • 怎么做家具网站/周口seo公司
  • jsp网站开发详解 下载/深圳白帽优化
  • 北京哪家做网站/百度关键词seo排名软件