--- id: 20260724-bounded-list-flex-height-chain title: 有界列表页使用连续 Flex 高度链并收敛滚动视口 created: 2026-07-24 updated: 2026-07-24 status: candidate scope: global category: frontend-layout confidence: medium last_verified: 2026-07-24 promotion_target: none projects: - usercenter-react tags: - flex-layout - min-height-zero - bounded-workspace - scroll-ownership - data-table --- # 有界列表页使用连续 Flex 高度链并收敛滚动视口 ## Trigger 列表页、主从工作台或树表页面需要填满应用内容区,同时保持标题、筛选、操作栏和分页可见,只让表格数据区、树节点区或详情正文滚动;或者当前页面出现双滚动条、分页被挤出视口、`h-full` 不生效、窗口高度变化后布局失效。 ## Context `usercenter-react` 的商场管理页面曾需要移除固定像素高度推算,使应用外壳、路由页面、Tabs、组织树和数据表形成连续的有界高度链。修复后,普通内容页仍由共享 `
` 滚动,有界工作台则填满 `
` 并把滚动交给最小内容视口。 这项经验的核心不是“给页面加 `flex-col`”,而是同时解决两个契约:高度从视口向后代连续传递,滚动权从外层收敛到最小内容区域。任一中间层缺少明确高度或 `min-h-0`,后代的 `h-full`、`flex-1` 和 `overflow-auto` 都可能无法按预期工作。 ## Evidence - 2026-07-24:重新检查 `usercenter-react` 当前源码、项目规范和 Git 历史;中央经验库搜索“有界列表页 / 连续 Flex 高度链 / min-h-0 / 滚动视口”无重叠条目。 - `usercenter-react/src/shared/layout/app-layout.tsx:207-236`:应用根节点使用 `h-dvh overflow-hidden`,内容列使用 `flex h-full min-h-0 flex-col`,共享 `
` 使用 `min-h-0 flex-1 overflow-y-auto`。 - `usercenter-react/src/features/market-management/pages/market-management-page.tsx:181-230`:有界路由根节点、分栏、Tabs 和 TabPane 连续使用 `h-full`、`flex-1`、`min-h-0` 与受控 overflow。 - `usercenter-react/src/shared/table/data-table.tsx:40-45,168-232`:`fillHeight` 以 opt-in 方式把表格面板变成有界 Flex 列,工具栏和分页留在滚动视口之外,数据区域消费剩余高度。 - `usercenter-react/.trellis/spec/frontend/layout-guidelines.md:23-32,74-125`:记录了各布局边界的类契约、滚动归属、错误矩阵和禁止使用固定 `calc(100vh - Npx)` 的规则。 - `usercenter-react/specs/page-layout.spec.md:5-11`:产品级规范要求有界页面使用连续 Flex 高度链,并将 overflow 委托给最小树或数据视口。 - Git 提交 `ccf068c` 修复商场管理页面高度适配,`1bb14e8` 将实践整理为 bounded layout 指南,`849219a` 继续补充可伸缩侧栏场景下的表格滚动与 sticky 边界。 - 当前仓库的 `src/shared/list-templates/master-detail-list-template.tsx:34-60` 仍存在 `calc(100vh - 10.5rem)` 实现;这是需要结合响应式父级契约进一步验证的现有反例,不能仅凭规则直接判定或修改。 ## Root cause 已验证: - `flex-col` 和 `flex-1` 只负责空间分配,不会自动建立可解析的高度边界。 - Flex/Grid 子项默认的 `min-height: auto` 会阻止内容区域收缩;中间层缺少 `min-h-0` 时,内容会撑高页面而不是在指定视口内滚动。 - `h-full` 依赖祖先提供确定高度;高度链中断时,百分比高度不能可靠地代表剩余视口空间。 - 页面、共享 `
` 和表格数据区同时拥有纵向 overflow 时,会形成嵌套或竞争滚动条。 - `calc(100vh - Npx)`、JavaScript 测量和重复的标题高度常量容易在工具栏换行、外壳调整、缩放或窄屏布局下失效。 推断:把布局评审固定为“先从外向内检查高度链,再从内向外确认唯一滚动所有者”,比逐个添加高度类或 overflow 类更容易定位缺失环节,也更适合跨组件复用。 ## Preferred action 1. 先区分页面模式:普通内容页继续由应用 `
` 滚动;只有需要固定控制区和内部滚动的页面才进入有界模式。 2. 从视口向目标滚动区域建立连续高度链:应用根节点建立动态视口边界,中间内容列使用 `flex h-full min-h-0 flex-col`,有界路由根节点使用 `flex h-full min-h-0 flex-col overflow-hidden`。 3. 为所有需要消费剩余高度或继续向下传递高度的 Flex/Grid 中间层添加 `min-h-0`;不要只修改最终表格容器。 4. 标题、筛选、操作栏、Tabs 头部和分页等固定区域使用 `shrink-0`。剩余内容区域使用 `min-h-0 flex-1`。 5. 将纵向滚动交给最小内容视口:通常是表格数据区、树节点区或详情正文。结构层使用 `overflow-hidden`,实际内容视口使用 `overflow-auto` 或 `overflow-y-auto`。 6. 共享表格的填高能力保持 opt-in;只有父级已经提供有界 `h-full/min-h-0` 区域时才启用,避免改变普通内容型页面的默认行为。 7. 优先使用 Flex 剩余空间,不使用固定 `calc(100vh - Npx)`、JavaScript 高度测量或跨组件共享的像素常量。 8. 在真实浏览器中验证滚动归属:有界页面不增加 body 高度,固定控制区和分页保持可见,只有目标数据区滚动;同时检查窄屏、工具栏换行、loading、empty 和 error 状态。类型检查、lint 和构建不能替代这项验证。 ## Examples ```tsx
{header}
{filtersAndActions}
``` 记忆方式:高度从外向内连续传递,滚动权收敛到最小内容视口。 ## Boundaries - 普通详情页、表单页和自然增长的长页面通常应继续由共享 `
` 滚动,不要为了统一外观强制启用有界模式。 - 移动端主从区域改为纵向堆叠时,可能更适合自然页面滚动;有界模式可以只在桌面断点启用。 - `overflow-hidden` 只有在后代明确拥有滚动视口时才安全,否则会直接裁切内容。 - Tabs、Drawer、Spin、Table 和虚拟列表等第三方组件可能插入包装层,需要根据实际 DOM 补齐高度链或沿用组件自己的滚动机制。 - 可交互变宽的分栏可能触发第三方表格的 ResizeObserver 和 sticky 行为;`nativeStickyHeader`、固定列实现等属于组件或项目特例,不是连续 Flex 高度链的通用要求。 - 已存在的 `calc(100vh - Npx)` 不应机械替换;需要先确认页面父级是否能提供完整高度链,以及窄屏模式是否依赖自然内容高度。 - 当前证据主要来自 `usercenter-react` 的一次完整修复及后续演进。在另一个项目或独立场景复用并完成浏览器验证前,不提升为全局强制规则。 ## Failed approaches - 只在页面根节点增加 `flex-col` 或只在表格增加 `flex-1`:高度链仍可能在中间层中断。 - 只给最终内容区域增加 `overflow-auto`:默认 `min-height: auto` 仍可能让它撑高父级。 - 同时让 `
`、路由根节点和表格数据区滚动:容易出现双滚动条、滚轮焦点切换和分页被挤出视口。 - 通过 `calc(100vh - Npx)` 或 JavaScript 测量补偿标题高度:在标题、操作栏换行或应用外壳变化后容易产生新的固定偏差。 ## Promotion record - Not promoted. 当前已有一个项目中的修复、规范和后续演进证据,先保留为中央 candidate;待第二个项目或独立场景验证后,再考虑提升为 `patterns/` 下的前端布局模式或可执行浏览器检查。