frontend: let the list tables fill the window instead of their rows

A list page is a table with controls around it, and the table was as tall as
its rows: on a large screen most of the window sat empty below the last row
while the page stayed the thing that scrolled. The three list pages now stretch
the table to the height the window offers — the toolbar and the pagination stay
put and the rows scroll inside the table — and give the height back on a small
screen.

Pure CSS, no resize listener and no measured pixel height, so nothing goes
stale when the rail collapses or the window is resized. Four links carry it,
each load-bearing: `.page--fill` is `min-height: 100%` (a floor, not a cap),
`.card--fill` makes the card a column, its body gets `min-height: 0` so a flex
child may shrink below its content, and `.table-fill` is `flex: 1 1 0` so the
table takes the leftover height rather than claiming its content height as its
basis. Element Plus then pins the header and scrolls the rows.

Verified by rendering the pages in a headless browser at 1920x1080, 1440x900
and 1280x620: the table measures 781/601/321px, the page never scrolls, the
rows scroll inside it, header and body columns stay aligned to 0px through a
400px horizontal scroll, the pinned action column stays pinned, and the empty
state is centred in the full-height body rather than stranded at the top. A
1280x420 window hits the 240px floor and falls back to the page scrolling, and
the paper document page is untouched.
This commit is contained in:
2026-09-18 18:26:52 +08:00
parent a5f884f440
commit 61857c3ce1
5 changed files with 104 additions and 9 deletions
+35
View File
@@ -298,6 +298,41 @@ module rather than CSS custom properties because `el-aside` takes its width
through a prop, which would otherwise fight an inline style coming from the
stylesheet.
### List pages fill the window
A table is the page. Left alone it is as tall as its rows, so on a large screen
most of the window sits empty below the last row while the *page* is still the
thing that scrolls — the wrong half of the screen moving for a list that should
simply show more. The three list pages (`/papers`, `/templates/list`,
`/templates/fields`) therefore stretch the table to the height the window
offers, with the toolbar above it and the pagination below it staying put while
the rows scroll inside the table.
It is CSS only — no resize listener, no measured pixel height, nothing to go
stale when the rail collapses or the window changes. Four links carry it, and
each one is load-bearing:
| Piece | Why |
|---|---|
| `.page--fill` | `min-height: 100%` of the content area — a floor that still grows |
| `.card--fill` | makes the card a column so its body can pass the height down |
| `.card--fill > .el-card__body` | a column with `min-height: 0`, without which a flex child refuses to shrink below its content |
| `.table-fill` + `height="100%"` | `flex: 1 1 0` so the table takes the leftover height instead of claiming its content height as its basis; Element Plus then pins the header and scrolls the rows |
Measured in a real browser at three window sizes, with rows to scroll and with
none:
| Window | Table | Page scroll | Rows scroll | Empty state |
|---|---|---|---|---|
| 1920x1080 | 781px | none | 519px inside | centred, 741px |
| 1440x900 | 601px | none | 699px inside | centred, 561px |
| 1280x620 | 321px | none | 979px inside | centred, 281px |
`.table-fill` keeps a 240px floor. Below it — a 1280x420 window — the table
stops shrinking, the page grows past the viewport and the content area scrolls,
which is the pre-existing behaviour rather than a squashed table. Pages that
are not lists (the paper document) keep scrolling normally.
### Routes
| Path | View | Section |
+57
View File
@@ -202,3 +202,60 @@ samp {
justify-content: flex-end;
margin-top: 16px;
}
/* --- list pages that fill the viewport --------------------------------------
A list page is a table with controls around it. Left alone, the table is as
tall as its rows, so on a large screen most of the window sits empty under
the last row while the page is still the thing that scrolls. These three
classes make the table take the height the window offers instead: more rows
visible at once on a big screen, and the height given back on a small one.
Every link in the chain is load-bearing:
.page--fill `min-height`, never `height`
.card--fill a column, so its body can pass the height on
.card--fill > .el-card__body
a column too, and `min-height: 0` — without it
a flex child refuses to shrink below its
content and the table would never get a height
.table-fill `flex: 1 1 0`, i.e. basis 0. With `auto` the
table would claim its content height as its
basis and push the pagination out of the card
el-table height="100%" fills .table-fill; Element Plus then keeps the
header fixed and scrolls the rows internally
`.page--fill` is a *minimum*, not a fixed height: on a short window the
table keeps its 240px floor, the page grows past the viewport and the
content area scrolls, which is the behaviour a squeezed table would not
survive. */
.page--fill {
min-height: 100%;
}
.card--fill {
/* Basis auto, so the card fills the page's minimum height exactly and still
grows if its content needs more. */
flex: 1 1 auto;
display: flex;
flex-direction: column;
min-height: 0;
}
.card--fill > .el-card__header {
flex: 0 0 auto;
}
.card--fill > .el-card__body {
flex: 1 1 auto;
min-height: 0;
display: flex;
flex-direction: column;
}
.table-fill {
/* `1 1 0` rather than `1 1 auto`: the table takes the leftover height rather
than adding to it. The min-height is the floor a short window lands on. */
flex: 1 1 0;
min-height: 240px;
}
+4 -3
View File
@@ -168,8 +168,8 @@ onMounted(async () => {
</script>
<template>
<div class="page">
<el-card shadow="never">
<div class="page page--fill">
<el-card shadow="never" class="card--fill">
<template #header>
<div class="card-header">
<div>
@@ -230,7 +230,8 @@ onMounted(async () => {
:data="rows"
row-key="id"
stripe
class="table"
class="table table-fill"
height="100%"
@selection-change="(value: PaperListItem[]) => (selection = value)"
>
<el-table-column type="selection" width="46" reserve-selection />
@@ -135,8 +135,8 @@ onMounted(load)
</script>
<template>
<div class="page">
<el-card shadow="never">
<div class="page page--fill">
<el-card shadow="never" class="card--fill">
<template #header>
<div class="card-header">
<div>
@@ -193,7 +193,8 @@ onMounted(load)
:data="rows"
row-key="id"
stripe
class="table"
class="table table-fill"
height="100%"
@selection-change="(value: TemplateFieldLibrary[]) => (selection = value)"
>
<el-table-column type="selection" width="46" reserve-selection />
@@ -140,8 +140,8 @@ onMounted(load)
</script>
<template>
<div class="page">
<el-card shadow="never">
<div class="page page--fill">
<el-card shadow="never" class="card--fill">
<template #header>
<div class="card-header">
<div>
@@ -187,7 +187,8 @@ onMounted(load)
:data="rows"
row-key="id"
stripe
class="table"
class="table table-fill"
height="100%"
@selection-change="(value: TemplateListItem[]) => (selection = value)"
>
<el-table-column type="selection" width="46" reserve-selection />