Align Fresh Mint §7.6 and OpenSpec ui-conventions with the confirmed UX; include design and plan. Co-authored-by: Cursor <cursoragent@cursor.com>
7.0 KiB
7.0 KiB
web-admin 列表表体内嵌滚动(视口填满)
日期: 2026-09-01
状态: 已确认
范围: web-admin 主内容区业务列表(list-body + el-table/ListTable + list-pager-bar);弹窗内小表、小程序不在本期。
交互 Demo: docs/list-table-scroll-demo.html
背景
用户反馈两个问题:
- 横向滚动条在表最底部:数据行很多时,必须滚到表底才能横滚,操作不便。
- 纵向滚到中部后看不到表头:列标题随页面滚走,难以对照列含义。
根因
多数业务列表未给表格设定可视高度,整表随 #app-content 页面滚动:
- 横滚发生在「整张表」的底部(行越多越远)
- 表头不是表体内 sticky / EP 固定头,纵滚后离开视口
规范 §7.6 与 fresh-mint-green.html 已描述 .table-scroll + max-height + sticky 表头,但业务落地不一致;部分模块(bx 等)仅有 overflow-x: auto、无高度约束,问题仍在。
决策摘要
| 项 | 结论 |
|---|---|
| 方案 | A:表体内嵌滚动(el-table / ListTable 动态 height) |
| 高度策略 | 尽量撑满视口;分页条始终可见;分页底边到内容区底留 10px |
| 不做 | 方案 B(页面滚 + 悬浮横条)、方案 C(顶部镜像横条)本期不做 |
| 非目标 | 弹窗内明细小表默认行为、小程序、改分页交互本身 |
布局与高度
纵向结构(落地后)
#app-content 可视区域
│ 内容区 padding(现有,如 24px)
│ page-head / panel
│ filter-block(可选,高度可变)
│ list-toolbar
│ ┌─ 表(height = 动态)────────────┐
│ │ thead 固定在表体顶 │
│ │ tbody 纵向滚动 │
│ │ 横向滚动条贴表体可视区底部 │
│ └──────────────────────────────────┘
│ pagination.list-pager-bar(始终可见)
│ ░░░░░ 10px ░░░░░ ← 分页底边 → 内容区底
高度公式
tableHeight = floor( contentViewportBottom - tableTop - pagerHeight - 10 )
tableHeight = max(tableHeight, MIN_HEIGHT) // 建议 MIN_HEIGHT = 240
- contentViewportBottom:主内容滚动容器(
#app-content)的可视底边(或等价测量点),而非整窗window,避免顶栏/Tab 误差。 - tableTop:表格根节点相对同一坐标系的顶边。
- pagerHeight:同页
.pagination.list-pager-bar实际高度(含 padding);无分页时该项为 0,底隙 10px 仍保留在表底与内容区底之间。 - 10px:产品确认——距底空隙在分页下方,不是表体下方挤掉分页。
重算时机
- 窗口 / 内容区
resize - 筛选区展开/收起、工具栏换行导致表顶变化
- 路由进入 / Tab 显示后(
nextTick) - 可选:
ResizeObserver观察筛选区、工具栏、分页条
重算后对表格调用 doLayout()(若持有 ListTable/el-table ref)。
架构
公共能力
新增 composable,建议名:useListTableViewportHeight(路径:web-admin/src/composables/)。
输入(示意):
- 表格锚点 ref(测
tableTop) - 分页条 ref(测
pagerHeight;可空) - 可选:
minHeight(默认 240)、bottomGap(默认 10) - 可选:内容滚动根选择器 / ref(默认
#app-content)
输出:
tableHeight: Ref<number>— 绑定:height="tableHeight"recalc()— 供页面在筛选展开等时机手动触发
不在 ListTable 内隐式写死高度(避免弹窗/嵌套表误伤);由列表页显式接入。
页面接入约定
- 保持结构:
list-body.list-wrap→ 表 →.pagination.list-pager-bar。 - 表使用
:height="tableHeight"(优先height而非仅max-height,以便空数据/少行时高度稳定)。 - 横向/纵向滚动交给 Element Plus 表体;不要再在外层
.table-scroll上设与表体冲突的overflow-y: auto+ 另一套 max-height(若保留.table-scroll仅作横向兜底,须与 EP 高度模式兼容,推荐 wrap 仅min-width: 0)。 - 操作列继续
fixed="right";滚动在 EP 内部时与现有全局「list-wrap避免乱设 overflow:hidden」规则不冲突。
与现有代码关系
| 现状 | 处理 |
|---|---|
规范 §7.6 max-height: min(520px, …) |
更新为「视口填满 − 分页 − 10px」,去掉写死 520px 作为主策略 |
bx .table-scroll { overflow-x: auto; overflow-y: hidden } |
接入动态 height 后,以 EP 内滚为准,避免双滚动容器 |
已有 :max-height="400" 等局部小表 |
弹窗/统计卡小表保持原样;仅主列表替换为视口高度策略 |
边界情况
| 场景 | 行为 |
|---|---|
| 筛选展开导致表区过矮 | height 不低于 MIN_HEIGHT(240);超出部分由 #app-content 外层滚动 |
| 展开行 / 树表 | 内容在表体内滚动;展开/收起后 doLayout |
| 空数据 / 行很少 | 仍使用同一动态 height,避免空态高度跳动、横条位置不稳 |
| 无分页列表 | pagerHeight = 0,表底到内容区底留 10px;注意无分页时 list-wrap 圆角裁剪既有规则仍适用 |
| 窄屏 | 公式不变;内容区 padding 变化由测量自然消化 |
| 双表 / Tab 多列表 | 每个可见列表各自锚点 + 各自 height;隐藏 Tab 可暂停观察或显示时再算 |
推广节奏
- 实现 composable + 文档/规范 §7.6 修订 + Demo 页(已有
docs/list-table-scroll-demo.html)对齐文案。 - 先接入模板参考页与高频
ListTable列表(如 system 模板页、bx/pl/db 已统一结构的列表)验证。 - 其余业务列表按模块批量接入;新建列表默认接入。
- OpenSpec
ui-conventions增加对应 SHALL(与 Fresh Mint 文档同步)。
验收标准
- 数据行较多时,纵向滚到表体中部:表头仍可见。
- 数据行较多且列溢出时:不必滚到最后一行即可使用横向滚动条(横条在表体可视区底部)。
- 分页条始终在视口内可见,其下到内容区底约 10px。
- 窗口缩放、筛选展开/收起后,高度与底隙仍正确。
- 操作列
fixed="right"、勾选列表现正常。 - 空数据时表区高度不塌缩到「仅表头+空文案」的跳动态(与有数据时同策略)。
非目标(重申)
- 顶部/悬浮镜像横向滚动条(方案 C/B)
- 小程序列表滚动
- 弹窗内嵌套明细表的默认视口填满(可按页单独
max-height) - 虚拟滚动 / 分页大小策略变更
参考
- 交互对比 Demo:
docs/list-table-scroll-demo.html - UI 规范:
docs/web-admin-ui-spec-fresh-mint.md§7.5–7.6 - HTML 基线:
docs/fresh-mint-green.html(.table-scroll) - 列表封装:
web-admin/src/components/business/list-table/index.vue