【sgSpliter】自定义组件:可调整宽度、高度、折叠的分割线
特性允许设置显示折叠按钮允许设置折叠线按钮位置允许设置当拖拽区域到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

相关新闻

Caveman生活方式指南:从原始饮食、数字断食到周末穴居实验

Caveman生活方式指南:从原始饮食、数字断食到周末穴居实验

我自己做生活方式研究这些年,发现一个很有意思的现象:越是在数字化、城市化走到极致的环境里,越多人开始回头研究“caveman”。这个词乍一看是“穴居人”“原始人”的意思,带着点粗粝感和玩笑气,但真去挖它的内涵&…

2026/10/7 16:04:29 阅读更多 →
Superpowers:现代AI编程工具链的三层认知增强架构

Superpowers:现代AI编程工具链的三层认知增强架构

1. 项目概述:Superpowers 不是超能力,而是开发者工具链的“认知增强层”最近在多个技术社区和开发者群聊里,“superpowers”这个词高频出现,但它既不是漫威电影里的变种人设定,也不是某个新出的AI模型代号——它是一套…

2026/10/7 16:04:29 阅读更多 →
Vibe Coding 实战:一天用 Claude 构建香港通勝查询站

Vibe Coding 实战:一天用 Claude 构建香港通勝查询站

1. 项目缘起与整体思路拆解1.1 为什么选这个题目:一个周末的极限挑战先说清楚这个项目到底在做什么。标题里提到的“香港通勝”,是粤港澳地区非常流行的一种传统民俗历书,内容涵盖每日宜忌、节气、生肖运程、吉凶方位等。传统做法是每年年底出…

2026/10/7 16:04:29 阅读更多 →

最新新闻

基于大语言模型的智能审计问答系统:源码解析与部署实战

基于大语言模型的智能审计问答系统:源码解析与部署实战

简介:基于大语言模型的智能审计问答系统源码,适用于毕业设计、期末大作业或课程设计场景,使用者可快速搭建一套完整可演示的审计问答平台。项目围绕智能审计业务设计问答逻辑,代码包含清晰注释,新手亦能读懂&#xff0…

2026/10/7 16:47:32 阅读更多 →
GESP八级选择判断备考:客观题考点、命题规律与丢分陷阱

GESP八级选择判断备考:客观题考点、命题规律与丢分陷阱

GESP八级是这套认证体系里的最高一级,很多人备考时把八成精力都砸在编程大题上,觉得能写出线段树和树形DP就算稳了。但真到了考场上,那十几道选择题加判断题才是决定过不过的关键——客观题合计分数通常在50分上下,错五道就是十分…

2026/10/7 16:47:32 阅读更多 →
短视频点赞任务平台源码:从部署到打包APP的完整闭环

短视频点赞任务平台源码:从部署到打包APP的完整闭环

简介:一套最新修复的抖音、快手、火山视频点赞任务平台源码,面向希望快速搭建任务分发系统或进行二次开发的运营者和开发者。平台包含用户注册、任务发布、点赞提交、后台审核、支付提现等典型流程,基于宝塔面板PHP7.0、Apache 2.4、MySQL 5.…

2026/10/7 16:47:32 阅读更多 →
鄱阳湖水系shp在ArcGIS中的处理:坐标系转换与提取指南

鄱阳湖水系shp在ArcGIS中的处理:坐标系转换与提取指南

简介:长江流域鄱阳湖水系地形图数据包,面向 ArcGIS 使用者及 GIS 入门学习者,提供湖泊、河流矢量图层与 DEM 栅格地形图,可直接用于流域制图、空间分析或科研底图。压缩包共 71 个文件,约 62.91MB,包含 shp…

2026/10/7 16:47:32 阅读更多 →
Spark on YARN内存超限解析:提交期到运行期的排查与修复

Spark on YARN内存超限解析:提交期到运行期的排查与修复

接手这个报错之前,我一直以为“Spark资源超限”就是executor给大了而已。直到有次凌晨值班,明明把 spark.executor.memory 从8g降到了6g,作业提交时还是被YARN一口回绝,才意识到这个内存阈值问题背后涉及的不只是Spark一侧的配置…

2026/10/7 16:47:32 阅读更多 →
微信登录三场景详解:从OAuth2到C#后端排错指南

微信登录三场景详解:从OAuth2到C#后端排错指南

很多人找我调微信登录的 bug 时,第一句话往往是“登录不好使”。等我远程看一眼代码,十有八九是把网页扫码登录和小程序登录的逻辑搅在一起了。微信登录这个功能,看起来就是一个授权弹窗,背后其实是好几条完全独立的链路&#xff…

2026/10/7 16:46:32 阅读更多 →

日新闻

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:01:58 阅读更多 →
用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →
芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 14:34:12 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 14:34:13 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 9:29:10 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 14:34:12 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 11:43:46 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 13:34:55 阅读更多 →