Files
shanxia2.0/docs/superpowers/specs/2026-09-01-web-admin-list-table-viewport-scroll-design.md
T
zlwuandCursor b8efcca1cf docs: specify viewport-fill list table scrolling
Align Fresh Mint §7.6 and OpenSpec ui-conventions with the confirmed UX; include design and plan.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 18:47:48 +08:00

7.0 KiB
Raw Blame History

web-admin 列表表体内嵌滚动(视口填满)

日期: 2026-09-01
状态: 已确认
范围: web-admin 主内容区业务列表(list-body + el-table/ListTable + list-pager-bar);弹窗内小表、小程序不在本期。
交互 Demo: docs/list-table-scroll-demo.html

背景

用户反馈两个问题:

  1. 横向滚动条在表最底部:数据行很多时,必须滚到表底才能横滚,操作不便。
  2. 纵向滚到中部后看不到表头:列标题随页面滚走,难以对照列含义。

根因

多数业务列表未给表格设定可视高度,整表随 #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 内隐式写死高度(避免弹窗/嵌套表误伤);由列表页显式接入。

页面接入约定

  1. 保持结构:list-body.list-wrap → 表 → .pagination.list-pager-bar。
  2. 表使用 :height="tableHeight"(优先 height 而非仅 max-height,以便空数据/少行时高度稳定)。
  3. 横向/纵向滚动交给 Element Plus 表体;不要再在外层 .table-scroll 上设与表体冲突的 overflow-y: auto + 另一套 max-height(若保留 .table-scroll 仅作横向兜底,须与 EP 高度模式兼容,推荐 wrap 仅 min-width: 0)。
  4. 操作列继续 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 可暂停观察或显示时再算

推广节奏

  1. 实现 composable + 文档/规范 §7.6 修订 + Demo 页(已有 docs/list-table-scroll-demo.html)对齐文案。
  2. 先接入模板参考页与高频 ListTable 列表(如 system 模板页、bx/pl/db 已统一结构的列表)验证。
  3. 其余业务列表按模块批量接入;新建列表默认接入。
  4. 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