108 lines
7.8 KiB
Markdown
108 lines
7.8 KiB
Markdown
|
|
---
|
|||
|
|
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、组织树和数据表形成连续的有界高度链。修复后,普通内容页仍由共享 `<main>` 滚动,有界工作台则填满 `<main>` 并把滚动交给最小内容视口。
|
|||
|
|
|
|||
|
|
这项经验的核心不是“给页面加 `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`,共享 `<main>` 使用 `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` 依赖祖先提供确定高度;高度链中断时,百分比高度不能可靠地代表剩余视口空间。
|
|||
|
|
- 页面、共享 `<main>` 和表格数据区同时拥有纵向 overflow 时,会形成嵌套或竞争滚动条。
|
|||
|
|
- `calc(100vh - Npx)`、JavaScript 测量和重复的标题高度常量容易在工具栏换行、外壳调整、缩放或窄屏布局下失效。
|
|||
|
|
|
|||
|
|
推断:把布局评审固定为“先从外向内检查高度链,再从内向外确认唯一滚动所有者”,比逐个添加高度类或 overflow 类更容易定位缺失环节,也更适合跨组件复用。
|
|||
|
|
|
|||
|
|
## Preferred action
|
|||
|
|
|
|||
|
|
1. 先区分页面模式:普通内容页继续由应用 `<main>` 滚动;只有需要固定控制区和内部滚动的页面才进入有界模式。
|
|||
|
|
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
|
|||
|
|
<div className="h-dvh overflow-hidden">
|
|||
|
|
<div className="flex h-full min-h-0 flex-col">
|
|||
|
|
<main className="min-h-0 flex-1 overflow-y-auto">
|
|||
|
|
<section className="flex h-full min-h-0 flex-col overflow-hidden">
|
|||
|
|
<header className="shrink-0">{header}</header>
|
|||
|
|
<div className="shrink-0">{filtersAndActions}</div>
|
|||
|
|
<div className="min-h-0 flex-1 overflow-hidden">
|
|||
|
|
<DataTable fillHeight {...tableProps} />
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
</main>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
记忆方式:高度从外向内连续传递,滚动权收敛到最小内容视口。
|
|||
|
|
|
|||
|
|
## Boundaries
|
|||
|
|
|
|||
|
|
- 普通详情页、表单页和自然增长的长页面通常应继续由共享 `<main>` 滚动,不要为了统一外观强制启用有界模式。
|
|||
|
|
- 移动端主从区域改为纵向堆叠时,可能更适合自然页面滚动;有界模式可以只在桌面断点启用。
|
|||
|
|
- `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` 仍可能让它撑高父级。
|
|||
|
|
- 同时让 `<main>`、路由根节点和表格数据区滚动:容易出现双滚动条、滚轮焦点切换和分页被挤出视口。
|
|||
|
|
- 通过 `calc(100vh - Npx)` 或 JavaScript 测量补偿标题高度:在标题、操作栏换行或应用外壳变化后容易产生新的固定偏差。
|
|||
|
|
|
|||
|
|
## Promotion record
|
|||
|
|
|
|||
|
|
- Not promoted. 当前已有一个项目中的修复、规范和后续演进证据,先保留为中央 candidate;待第二个项目或独立场景验证后,再考虑提升为 `patterns/` 下的前端布局模式或可执行浏览器检查。
|