背景与目标
在 Vue3 后台页面里,经常需要把两个区域用一条可拖拽的分割线隔开:上下分割或左右分割。实现时不要直接改百分比高度,而应按“容器约束 + useResizableRegion + BaseResizer + flex CSS + 回归测试”五步推进。核心是让 hook 拿到容器尺寸,并让固定区域尺寸由 JS 控制、自适应区域由 flex 占满。
一、先确认容器和分割方向
分割线夹在两个区域之间,这两个区域和分割线必须放在同一个容器中。hook 计算固定区域最大尺寸的公式是:- max = 容器尺寸 - 分割线尺寸 - 自适应区域最小尺寸
复制代码 因此容器里只能装“固定区域 + 分割线 + 自适应区域”三件套。若混入操作栏、工具栏、分页等,容器尺寸会偏大,算出的 max 也偏大,拖到极限时自适应区域会被挤爆,出现表格少一行、底部或侧边边框被裁。修法是把操作栏移到 #header 插槽,容器内只保留两个待分割区域。
方向约定:上下分割用 direction='vertical',调整高度,固定上方区域,使用 HEIGHT 系列常量;左右分割用 direction='horizontal',调整宽度,固定左侧区域,使用 WIDTH 系列常量。固定上方或左侧的原因是拖拽时鼠标往下或往右,分割线移动后固定区域被撑大,尺寸能跟手。
二、声明容器 ref、常量和 useResizableRegion
先声明容器 ref,再声明固定区域默认尺寸、固定区域最小尺寸、自适应区域最小预留尺寸,最后把容器传给 hook:- import { ref, type ComponentPublicInstance } from 'vue';
- import { useResizableRegion } from '@/hooks';
- const containerRef = ref<ComponentPublicInstance | null>(null);
- const DEFAULT_FIXED_HEIGHT = 400;
- const MIN_FIXED_HEIGHT = 82;
- const MIN_ADAPTIVE_HEIGHT = 82;
- const { size: fixedHeight, getMax: calcMaxFixedHeight } = useResizableRegion({
- direction: 'vertical',
- storageKey: 'report-prepare-fixed-height',
- defaultSize: DEFAULT_FIXED_HEIGHT,
- minSize: MIN_FIXED_HEIGHT,
- reserveSize: MIN_ADAPTIVE_HEIGHT,
- getContainer: () => containerRef.value
- });
复制代码 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
上下分割的模板结构如下:- <template>
- <div class='container' ref='containerRef'>
- <div class='fixed-region' :style='{ height: `${fixedHeight}px` }'>
- 固定区域内容
- </div>
- <BaseResizer
- v-model='fixedHeight'
- direction='vertical'
- variant='block'
- :min='MIN_FIXED_HEIGHT'
- :max='calcMaxFixedHeight'
- :default-size='DEFAULT_FIXED_HEIGHT' />
- <div class='adaptive-region'>
- 自适应区域内容
- </div>
- </div>
- </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 控制显示,需要让固定区域在隐藏时撑满,并让分割线同步隐藏:- <div class='fixed-region' :style='{ height: showAdaptiveRegion ? `${fixedHeight}px` : `100%` }'>
- ...
- </div>
- <BaseResizer v-show='showAdaptiveRegion' v-model='fixedHeight' ... />
- <div class='adaptive-region' v-show='showAdaptiveRegion'>
- ...
- </div>
复制代码 这样隐藏自适应区域时,固定区域不会保留旧尺寸造成留白,分割线也不会悬空。左右分割时把 height 换成 width。
四、CSS 用 flex 锁定布局
上下分割的 CSS 可以写成:- .container {
- display: flex;
- flex-direction: column;
- height: 100%;
- padding: 0;
- margin: 0;
- }
- .fixed-region {
- flex: none;
- overflow: hidden;
- padding: 0;
- margin: 0;
- }
- .adaptive-region {
- flex: 1;
- min-height: 82px;
- overflow: hidden;
- height: auto;
- padding: 0;
- margin: 0;
- }
复制代码 固定区域用 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 页面拖拽分割线。 |