☰
【sgSpliter】自定义组件:可调整宽度、高度、折叠的分割线
2026/10/7 16:04:04 网站建设 项目流程

特性:

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

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" :title="title" > <p class="text" v-if="arrowBtnText" v-html="arrowBtnText" /> <!-- 箭头在父组件的最左侧 --> <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、深蓝蓝色背景darkblue arrowBtnText: ``, //折叠按钮箭头上的文字 splitBarSize: 1, //可选,分隔条大小,默认 2px resizable: true, //可选,指定 是否可调整大小,会影响相邻 mouseoverShowArrowBtn: false, //外部触发显示折叠按钮 disabledDblclickSpliterLine: false, //禁止双击折叠线 title: ``, }; }, props: ["data"], computed: {}, watch: { data: { handler(newValue, oldValue) { // console.log(`深度监听${this.$options.name}:`, newValue, oldValue); if (Object.keys(newValue || {}).length) { this.form = this.$g.deepCopy(newValue); this.$g.cF2CP(`showArrowBtn`, this); this.$g.cF2CP(`defaultSize`, this); this.$g.cF2CP(`nearEdgeSize`, this); this.$g.cF2CP(`minSize`, this); this.$g.cF2CP(`maxSize`, this); this.$g.cF2CP(`placement`, this); this.$g.cF2CP(`parent`, this); this.$g.cF2CP(`arrowBtnStyleType`, this); this.$g.cF2CP(`arrowBtnText`, this); this.$g.cF2CP(`splitBarSize`, this); this.$g.cF2CP(`resizable`, this); this.$g.cF2CP(`mouseoverShowArrowBtn`, this); this.$g.cF2CP(`disabledDblclickSpliterLine`, this); this.$g.cF2CP(`title`, this); this.form.hasOwnProperty("collapse") && (oldValue || {}).collapse != (newValue || {}).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的时候,第二次不监听变化 setTimeout(() => { this.$nextTick(() => this.$emit(`sizeEnd`, { size: this.size })); }, 300); }, 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(); this.$nextTick(() => this.$emit(`sizeEnd`, { size: this.size })); }, }, }; </script> <style lang="scss" scoped> $splitBarSize: var(--splitBarSize); //css获取js传递的参数 $arrowBtnPadding: 10px; .sgSpliter { z-index: 1; background-color: #efefef; position: absolute; left: 0; top: 0; right: 0; bottom: 0; user-select: none; /*禁止选中文本*/ .arrow-btn { transition: 0.2s; opacity: 0; pointer-events: none; // transform: translateY(50%); //防止托盘最小高度的时候还冒出一小截 width: max-content; height: max-content; display: flex; justify-content: center; align-items: center; color: #409eff; background-color: white; font-size: 12px; text-align: center; position: absolute; margin: auto; box-sizing: border-box; cursor: pointer; &:hover { opacity: 0.9 !important; } &[styleType="darkblue"] { color: white; background-color: #1f2d3d; } &[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 { flex-direction: column; width: 20px; left: revert; right: $splitBarSize; top: 0; bottom: 0; border-radius: 8px 0 0 8px; padding: $arrowBtnPadding 0; box-shadow: -5px 0px 10px 0 rgba(0, 0, 0, 0.1); .text { line-height: 14px; margin-bottom: 5px; } } } &[placement="right"] { left: revert; right: 0; .arrow-btn { flex-direction: column; width: 20px; left: $splitBarSize; right: revert; top: 0; bottom: 0; border-radius: 0 8px 8px 0; padding: $arrowBtnPadding 0; box-shadow: 5px 0px 10px 0 rgba(0, 0, 0, 0.1); .text { line-height: 14px; margin-bottom: 5px; } } } &[placement="top"] { top: 0; bottom: revert; .arrow-btn { height: 20px; left: 0; right: 0; top: revert; bottom: $splitBarSize; border-radius: 8px 8px 0 0; padding: 0 $arrowBtnPadding; box-shadow: 0px -5px 10px 0 rgba(0, 0, 0, 0.1); .text { margin-right: 5px; } } } &[placement="bottom"] { top: revert; bottom: 0; .arrow-btn { height: 20px; left: 0; right: 0; top: $splitBarSize; bottom: revert; border-radius: 0 0 8px 8px; padding: 0 $arrowBtnPadding; box-shadow: 0px 5px 10px 0 rgba(0, 0, 0, 0.1); .text { margin-right: 5px; } } } // ---------------------------------------- &[mouseoverShowArrowBtn], &:hover { background-color: #b3d8ff; .arrow-btn { opacity: 1; pointer-events: auto; } } // 按下拖拽线条后出现的半透明区域 &::after { content: ""; transition: 0.2s; 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`, arrowBtnStyleType: `blue`, arrowBtnText: `菜单` }" @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`, arrowBtnStyleType: `darkblue`, arrowBtnText: `工具栏`, mouseoverShowArrowBtn: true,//一直显示 }" @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>

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询