React + Antd 全高度滚动模板(可直接复用)

1. FullHeightLayout.module.less

.pageRoot {
  height: 100%;
  min-height: 0;
}

.grid {
  display: flex;
  flex-direction: column;
  gap: 24px;
  width: 100%;
  height: auto;
  min-height: 0;
}

@media (min-width: 1200px) {
  .grid {
    display: grid;
    grid-template-columns: repeat(24, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
    gap: 24px;
    height: 100%;
    min-height: 0;
  }
}

.gridItem {
  width: 100%;
  height: auto;
  min-height: 0;
  min-width: 0;
}

@media (min-width: 1200px) {
  .gridItem {
    height: 100%;
    min-height: 0;
  }
}

.sectionCard {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.sectionInner {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Tabs 全高度链路 */
.tabsWrap {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.tabsWrap :global(.ant-tabs) {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.tabsWrap :global(.ant-tabs-content-holder) {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.tabsWrap :global(.ant-tabs-content) {
  height: 100%;
  min-height: 0;
}

.tabsWrap :global(.ant-tabs-tabpane) {
  height: 100%;
  min-height: 0;
}

/* List:搜索区固定 + 列表区内部滚动 */
.listPane {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.listHeader {
  flex-shrink: 0;
  padding-bottom: 12px;
}

.listBody {
  flex: 1;
  min-height: 0;
  height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.listBody :global(.ant-spin-nested-loading) {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.listBody :global(.ant-spin-container) {
  height: 100%;
  min-height: 0;
  overflow: auto;
}

.fillList {
  height: 100%;
}

/* Table:body 内部滚动 */
.tablePane {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.tableBody {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.tableBody :global(.ant-table-wrapper),
.tableBody :global(.ant-spin-nested-loading),
.tableBody :global(.ant-spin-container),
.tableBody :global(.ant-table),
.tableBody :global(.ant-table-container) {
  height: 100%;
  min-height: 0;
}

.tableBody :global(.ant-table-body) {
  overflow: auto !important;
}

2. FullHeightLayoutDemo.tsx

import React from 'react';
import { Card, Input, List, Table, Tabs } from 'antd';
import type { TabsProps, TableColumnsType } from 'antd';
import styles from './FullHeightLayout.module.less';

type RowData = {
  id: number;
  name: string;
  idNo: string;
};

const mockList: RowData[] = Array.from({ length: 30 }).map((_, i) => ({
  id: i + 1,
  name: `考生-${i + 1}`,
  idNo: `5002********${String(i).padStart(4, '0')}`,
}));

const mockCols: TableColumnsType<RowData> = [
  { title: 'ID', dataIndex: 'id', width: 80 },
  { title: '姓名', dataIndex: 'name' },
  { title: '证件号', dataIndex: 'idNo' },
];

const ListPane: React.FC = () => {
  return (
    <div className={styles.listPane}>
      <div className={styles.listHeader}>
        <Input.Search placeholder="考生查询" enterButton />
      </div>

      <div className={styles.listBody}>
        <List
          className={styles.fillList}
          dataSource={mockList}
          renderItem={(item) => (
            <List.Item key={item.id}>
              <Card style={{ width: '100%' }}>
                {item.name} - {item.idNo}
              </Card>
            </List.Item>
          )}
        />
      </div>
    </div>
  );
};

const TablePane: React.FC = () => {
  return (
    <div className={styles.tablePane}>
      <div className={styles.tableBody}>
        <Table<RowData>
          rowKey="id"
          columns={mockCols}
          dataSource={mockList}
          pagination={{ pageSize: 10 }}
        />
      </div>
    </div>
  );
};

const tabsItems: TabsProps['items'] = [
  { key: 'list', label: '列表', children: <ListPane /> },
  { key: 'table', label: '表格', children: <TablePane /> },
  { key: 'other', label: '其他', children: <div style={{ height: '100%' }}>内容</div> },
];

const FullHeightLayoutDemo: React.FC = () => {
  return (
    <div className={styles.pageRoot}>
      <div className={styles.grid}>
        <div className={styles.gridItem} style={{ gridColumn: 'span 18' }}>
          <Card className={styles.sectionCard} styles={{ body: { height: '100%', padding: 12 } }}>
            <div className={styles.sectionInner}>上半区内容</div>
          </Card>
        </div>

        <div className={styles.gridItem} style={{ gridColumn: 'span 6' }}>
          <Card className={styles.sectionCard} styles={{ body: { height: '100%', padding: 12 } }}>
            <div className={styles.sectionInner}>上半区右侧</div>
          </Card>
        </div>

        <div className={styles.gridItem} style={{ gridColumn: 'span 12' }}>
          <Card className={styles.sectionCard} styles={{ body: { height: '100%', padding: 12 } }}>
            <div className={styles.tabsWrap}>
              <Tabs items={tabsItems} />
            </div>
          </Card>
        </div>

        <div className={styles.gridItem} style={{ gridColumn: 'span 6' }}>
          <Card className={styles.sectionCard} styles={{ body: { height: '100%', padding: 12 } }}>
            <div className={styles.sectionInner}>左侧模块</div>
          </Card>
        </div>

        <div className={styles.gridItem} style={{ gridColumn: 'span 6' }}>
          <Card className={styles.sectionCard} styles={{ body: { height: '100%', padding: 12 } }}>
            <div className={styles.sectionInner}>右侧模块</div>
          </Card>
        </div>
      </div>
    </div>
  );
};

export default FullHeightLayoutDemo;

3. 日常修复检查清单(复制保存)

  1. Grid 第二行若要内部滚动,优先 minmax(0, 1fr)
  2. flex/grid 子项中,承担滚动链路的层补 min-height: 0
  3. 横向溢出时对应补 min-width: 0
  4. overflow: auto 放在真实内容层,不要只放最外层壳子。
  5. Antd 组件要检查中间层:ant-tabs-content-holderant-spin-containerant-table-body
  6. 避免 margin-top: -xx 这类负 margin 干扰滚动与可视区。