特性允许设置显示折叠按钮允许设置折叠线按钮位置允许设置当拖拽区域到0再点击箭头展开的默认宽度允许设置当拖拽宽度小于此宽度自动折叠到0允许设置指定最小宽度允许设置指定最大宽度允许设置按钮风格:白色背景default、蓝色背景blue、深蓝蓝色背景darkblue允许设置分隔条大小默认 2px允许设置指定 是否可调整大小会影响相邻允许设置外部触发显示折叠按钮允许设置禁止双击折叠线允许设置折叠按钮箭头上的文字sgSpliter.vuetemplate !-- 注意 父组件position必须是relative、absolute或fixed 不建议直接在绑定:data后面用{属性} 建议单独在script中声明data避免拖拽过程重复调用 -- div :class$options.name :placementplacement mousedown__addWindowEvents dblclickdblclickSpliterLine :unresizable!resizable :mouseoverShowArrowBtnmouseoverShowArrowBtn div v-ifshowArrowBtn classarrow-btn clickclickArrowBtn mousedown.stop :styleTypearrowBtnStyleType :collapsecollapse :titletitle p classtext v-ifarrowBtnText v-htmlarrowBtnText / !-- 箭头在父组件的最左侧 -- template v-ifplacement left i classel-icon-arrow-left v-ifcollapse / i classel-icon-arrow-right v-else / /template !-- 箭头在父组件的最右侧 -- template v-ifplacement right i classel-icon-arrow-right v-ifcollapse / i classel-icon-arrow-left v-else / /template !-- 箭头在父组件的最上侧 -- template v-ifplacement top i classel-icon-arrow-up v-ifcollapse / i classel-icon-arrow-down v-else / /template !-- 箭头在父组件的最下侧 -- template v-ifplacement bottom i classel-icon-arrow-down v-ifcollapse / i classel-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 }); //避免上一次size0的时候第二次不监听变化 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 langscss 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; } [styleTypedarkblue] { color: white; background-color: #1f2d3d; } [styleTypeblue] { color: white; background-color: #4f6bdf; } [collapse] { opacity: 1; pointer-events: auto; } } // 位置---------------------------------------- [placementleft], [placementright] { cursor: col-resize; width: $splitBarSize; height: 100%; } [placementtop], [placementbottom] { cursor: row-resize; width: 100%; height: $splitBarSize; } [placementleft] { 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; } } } [placementright] { 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; } } } [placementtop] { 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; } } } [placementbottom] { 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}); [placementleft], [placementright] { ::after { width: $splitOpacityBgSize; height: 100%; left: -#{$splitOpacityBgExpandSize}; top: 0; } } [placementtop], [placementbottom] { ::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 langscss [sgSpliter-transitionSize] { transition: 0.2s; } /styledemotemplate div :class$options.name div classleft :style{ width: ${leftWidth}px } sgSpliter :data{ placement: right } sizeChangeleftWidth $event.size / /div div classright div classtop :style{ height: ${topHeight}px } sgSpliter :data{ placement: bottom, arrowBtnStyleType: blue, arrowBtnText: 菜单 } sizeChangetopHeight $event.size / /div div classbottom div classleft div classtop/div div classbottom :style{ height: ${bottomHeight}px } sgSpliter :data{ placement: top, } sizeChangebottomHeight $event.size / /div /div div classright :style{ width: ${bottomWidth}px } sgSpliter :data{ placement: left, arrowBtnStyleType: darkblue, arrowBtnText: 工具栏, mouseoverShowArrowBtn: true,//一直显示 } sizeChangebottomWidth $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 langscss 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