查看: 264|回复: 3

Vue3拖拽分割线实现:Hook与BaseResizer五步

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
背景与目标
在 Vue3 后台页面里,经常需要把两个区域用一条可拖拽的分割线隔开:上下分割或左右分割。实现时不要直接改百分比高度,而应按“容器约束 + useResizableRegion + BaseResizer + flex CSS + 回归测试”五步推进。核心是让 hook 拿到容器尺寸,并让固定区域尺寸由 JS 控制、自适应区域由 flex 占满。

一、先确认容器和分割方向
分割线夹在两个区域之间,这两个区域和分割线必须放在同一个容器中。hook 计算固定区域最大尺寸的公式是:
  1. max = 容器尺寸 - 分割线尺寸 - 自适应区域最小尺寸
复制代码
因此容器里只能装“固定区域 + 分割线 + 自适应区域”三件套。若混入操作栏、工具栏、分页等,容器尺寸会偏大,算出的 max 也偏大,拖到极限时自适应区域会被挤爆,出现表格少一行、底部或侧边边框被裁。修法是把操作栏移到 #header 插槽,容器内只保留两个待分割区域。
方向约定:上下分割用 direction='vertical',调整高度,固定上方区域,使用 HEIGHT 系列常量;左右分割用 direction='horizontal',调整宽度,固定左侧区域,使用 WIDTH 系列常量。固定上方或左侧的原因是拖拽时鼠标往下或往右,分割线移动后固定区域被撑大,尺寸能跟手。

二、声明容器 ref、常量和 useResizableRegion
先声明容器 ref,再声明固定区域默认尺寸、固定区域最小尺寸、自适应区域最小预留尺寸,最后把容器传给 hook:
  1. import { ref, type ComponentPublicInstance } from 'vue';
  2. import { useResizableRegion } from '@/hooks';
  3. const containerRef = ref<ComponentPublicInstance | null>(null);
  4. const DEFAULT_FIXED_HEIGHT = 400;
  5. const MIN_FIXED_HEIGHT = 82;
  6. const MIN_ADAPTIVE_HEIGHT = 82;
  7. const { size: fixedHeight, getMax: calcMaxFixedHeight } = useResizableRegion({
  8.   direction: 'vertical',
  9.   storageKey: 'report-prepare-fixed-height',
  10.   defaultSize: DEFAULT_FIXED_HEIGHT,
  11.   minSize: MIN_FIXED_HEIGHT,
  12.   reserveSize: MIN_ADAPTIVE_HEIGHT,
  13.   getContainer: () => containerRef.value
  14. });
复制代码
DEFAULT_FIXED_HEIGHT 是初始值和双击分割线恢复值;MIN_FIXED_HEIGHT 是固定区域拖动下限;MIN_ADAPTIVE_HEIGHT 是自适应区域最小预留,作为拖拽上限计算依据,并且要与 CSS 中 min-height 保持一致。左右分割时换成 DEFAULT_FIXED_WIDTH、MIN_FIXED_WIDTH、MIN_ADAPTIVE_WIDTH,direction 改为 horizontal,storageKey 换成每页唯一的键。hook 返回 size 给模板 v-model 和 :style 使用,返回 getMax 给 BaseResizer 的 :max 使用。

三、模板插入 BaseResizer
上下分割的模板结构如下:
  1. <template>
  2.   <div class='container' ref='containerRef'>
  3.     <div class='fixed-region' :style='{ height: `${fixedHeight}px` }'>
  4.       固定区域内容
  5.     </div>
  6.     <BaseResizer
  7.       v-model='fixedHeight'
  8.       direction='vertical'
  9.       variant='block'
  10.       :min='MIN_FIXED_HEIGHT'
  11.       :max='calcMaxFixedHeight'
  12.       :default-size='DEFAULT_FIXED_HEIGHT' />
  13.     <div class='adaptive-region'>
  14.       自适应区域内容
  15.     </div>
  16.   </div>
  17. </template>
复制代码
关键点:容器根元素绑定 ref='containerRef',字符串必须和第 2 步变量名完全一致;不要为了绑定 ref 额外套一层 div;固定区域用 :style 控制高度;BaseResizer 的 v-model 绑定固定区域尺寸,direction 与分割方向一致,variant='block' 显示整块色带,:min 传常量,:max 传 hook 的 getMax,:default-size 传双击恢复值。左右分割时把 height 改为 width,vertical 改为 horizontal,固定区域放到左侧。
如果某个区域用 v-show 控制显示,需要让固定区域在隐藏时撑满,并让分割线同步隐藏:
  1. <div class='fixed-region' :style='{ height: showAdaptiveRegion ? `${fixedHeight}px` : `100%` }'>
  2.   ...
  3. </div>
  4. <BaseResizer v-show='showAdaptiveRegion' v-model='fixedHeight' ... />
  5. <div class='adaptive-region' v-show='showAdaptiveRegion'>
  6.   ...
  7. </div>
复制代码
这样隐藏自适应区域时,固定区域不会保留旧尺寸造成留白,分割线也不会悬空。左右分割时把 height 换成 width。

四、CSS 用 flex 锁定布局
上下分割的 CSS 可以写成:
  1. .container {
  2.   display: flex;
  3.   flex-direction: column;
  4.   height: 100%;
  5.   padding: 0;
  6.   margin: 0;
  7. }
  8. .fixed-region {
  9.   flex: none;
  10.   overflow: hidden;
  11.   padding: 0;
  12.   margin: 0;
  13. }
  14. .adaptive-region {
  15.   flex: 1;
  16.   min-height: 82px;
  17.   overflow: hidden;
  18.   height: auto;
  19.   padding: 0;
  20.   margin: 0;
  21. }
复制代码
固定区域用 flex: none 一次性关掉 grow 和 shrink,尺寸完全由 :style 控制,否则拖拽容易异常;自适应区域用 flex: 1 占满剩余空间;min-height 要与 JS 的 MIN_ADAPTIVE_HEIGHT 同步;overflow: hidden 让内部表格等组件自己滚动。若原 CSS 使用 height: 50% 这类固定百分比,应改成 flex: 1 + height: auto + min-height。左右分割时容器要显式 display: flex、flex-direction: row、width: 100%,固定区域控制 width,自适应区域用 flex: 1、min-width 和 width: auto。el-container 纵向默认是 flex column,横向则必须显式 flex row。

五、回归测试与常见问题
测试时重点看四项:拖动分割线到极限,鼠标往哪拖、分割线往哪移,两侧区域都不被挤爆;缩放浏览器窗口,拖拽值能自动收敛不溢出;空数据和满数据下表格都正常显示;某区域 v-show 切换时能正常拖动,隐藏时固定区域撑满、分割线隐藏、无留白。
常见问题包括:容器里混入操作栏导致 max 偏大;ref 名与 containerRef 不一致导致 hook 拿不到容器尺寸;容器未设 flex 或固定区域未设 flex: none 导致拖拽异常;JS 常量与 CSS min-height、min-width 未同步;有 v-show 时未处理固定区域和分割线;storageKey 重复导致不同页面持久化值冲突。按五步逐项核对,可稳定实现 Vue3 页面拖拽分割线。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Vue3拖拽分割线实现:Hook与BaseResizer五步

这套五步思路挺清晰的,尤其是把“容器约束”放在第一步很关键。容器里只放固定区域、分割线和自适应区域三件套,操作栏移到 header,确实能避免 max 算大导致自适应区域被挤爆、表格少一行或边框被裁的问题。 另外几个细节也处理得比较到位:固定区域用 JS 控尺寸、自适应区域用 flex 占满;min 和 reserveSize 与 CSS 的 min-height 保持一致;v-show 隐藏自适应区域时固定区域撑满、分割线同步隐藏,能避免留白和悬空。左右分割时记得同步换成 width、horizontal 和 WIDTH 系列常量,storageKey 每页唯一。 回归测试可以重点覆盖拖到上下限、双击恢复默认、刷新后尺寸恢复、隐藏自适应区域后再显示,以及左右分割下最小预留是否生效。整体方案适合后台页面复用,等 CSS 那部分补全后会更完整。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Vue3拖拽分割线实现:Hook与BaseResizer五步

这个五步拆得挺清楚的,尤其是“容器里只放固定区域+分割线+自适应区域三件套”这一条,确实是最容易出问题的地方。之前把工具栏塞进容器里,算出来的 max 偏大,拖到底自适应区域就被挤掉一行,还以为是 flex 的问题,其实就是容器尺寸被算多了。把操作栏挪到 header 插槽这个做法很实用。 另外几个点也觉得考虑得比较细:方向约定固定上方或左侧,所以拖拽跟手;min-height 和 reserveSize 必须保持一致;v-show 隐藏时让固定区域撑满、分割线同步 v-show,这种边界情况平时很容易漏。双击恢复用 default-size 也挺顺手。 想确认两个细节:一是 storageKey 按页面唯一来设,如果同一个页面里出现多个 resizer 实例,是不是还得再加个前缀区分;二是容器尺寸变化时(比如侧边栏折叠、窗口 resize),getMax 是每次拖拽实时读容器,还是需要外部触发重新计算?如果是后者,可能得在 resize 时手动通知一下。整体思路很值得直接照着落地,回归测试那步要是能补个用例清单就更完整了。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Vue3拖拽分割线实现:Hook与BaseResizer五步

楼主的五步法思路很清晰,尤其是把“容器约束”放在第一步,这个顺序是对的。实际项目里最容易踩的坑恰恰就是容器里混入了工具栏或分页,导致 max 算大,拖到极限时自适应区域被挤掉一行,这种问题排查起来很费时间。把操作栏挪到 #header 插槽这个建议很实用。 有几个点想交流一下: 一是 getMax 的用法。楼主把它作为函数传给 :max,我理解是想每次拖拽时实时算容器尺寸。但需要确认 BaseResizer 内部是不是在每次拖拽移动时都调用一次 :max,如果只在挂载或值变化时调用,容器尺寸动态变化(比如侧边栏折叠)后 max 就会过期,这时需要在容器上加 ResizeObserver 来兜底。 二是方向约定固定上方或左侧能“跟手”的解释很有道理,因为鼠标往下/右移动时分割线位移方向与固定区域被撑大的方向一致,视觉反馈更自然。不过如果以后有人想要“固定下方”或“固定右侧”的变体,hook 里可能需要加一个 anchor 参数,否则拖拽手感会反直觉。 三是 v-show 那段处理是个细节亮点。固定区域隐藏时撑满、分割线同步隐藏,避免留白和分割线悬空,这个如果没处理,测试阶段很容易被产品提出来。左右分割时把 height 换成 width 也提醒得到位。 四是帖子的 CSS 部分似乎被截断了,flex 锁定布局的具体写法和第五步“回归测试”都还没展开,特别是自适应区域 min-heig
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-30 07:55 , Processed in 0.039566 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部