---
nav:
  title: 组件
  order: 1
group:
  title: Data Display
  order: 1
title: PisellRecordBoard
order: 1
category: pro
---

# PisellRecordBoard 记录看板

记录看板容器组件，提供统一的列表数据、分页、搜索/筛选/排序、多选等上下文，支持 Grid（表格）与 Card 两种布局。

## 组件介绍

PisellRecordBoard 是列表类页面的**容器**，不直接渲染表格或卡片，而是通过 Context 向子组件提供 `data`、`pagination`、
`searchParams`、`onSearch`、多选等。ToolBar 左侧为快速筛选（QuickFilter）+ 搜索（PisellFind），右侧为排序、筛选弹层、重置；列配置（
`grid.columns`）可驱动筛选项与排序项，无需单独维护 filter 列表。

**核心特性**：

- 统一上下文（data、分页、搜索参数、多选）
- 列驱动筛选/排序（有 type 自动派生，无 type 可自定义 filter/sort）
- 支持有 type 时用 render/filter/sort 覆盖
- 快速筛选（左侧内联）+ 高级筛选（右侧弹层）
- 多选与 BatchActionBar
- **可选平面图**：在 `ShellFrame` 内声明 **`PisellRecordBoard.FloorMap`**（与 `GridLayout` / `CardLayout` 并列）；**仅表格
  **、**仅平面图**、**双视图**（此时 ToolBar 出现「表格 / 平面图」）由你传了哪些子节点决定；根级 `floorMap` 仍兼容（如
  SalesGrid），与子组件 props 合并且 **子优先**。平面图模式下不显示底部分页

## 架构总览

### 组件树（推荐：ShellFrame + GridLayout [+ FloorMap]）

```
PisellRecordBoard（根容器：flex 列、minHeight:0，注入 RecordBoardProvider）
└─ PisellRecordBoard.ShellFrame（PisellShellFrame：toolbar + batch + 内容区）
   ├─ RecordBoardToolBar（搜索 / 快筛 / [可选] 表格·平面图切换 / 列设置 / …）
   ├─ RecordBoardBatchActionBar（有选中行时出现）
   ├─ 主内容区（由 ShellFrame 解析子节点）
   │  ├─ 子节点示例：<GridLayout /> 与 <FloorMap {...pisellFloorMapProps} /> 并列
   │  ├─ bodyView === 'grid'：ScrollAreaMeasurer（可选）→ 非 FloorMap 子树（如 GridLayout → Grid）
   │  └─ bodyView === 'floorMap'：PisellFloorMapLayout（props 来自 FloorMap + 可选根级 floorMap 合并）
   └─ RecordBoardPagination（仅 grid 子视图；平面图模式隐藏）
```

- **`layoutType`**（`'grid' | 'card'`）描述「内容区用哪套 Layout 组件集合」；**`bodyView`**（`'grid' | 'floorMap'`）在 *
  *存在平面图能力**（Shell 内出现 `FloorMap` 或根级传入 `floorMap`）且 **同时存在表格区子节点** 时，用于切换主内容；仅
  `FloorMap`、无 Grid/Card 时固定为平面图，无 Segmented。
- **数据不重复请求**：`data` / `loading` / `total` / `pagination` / `searchParams` / `onSearch` / `onPageChange`
  由上层统一维护（如 DataSource、useRequest）；RecordBoard 只负责 Context 分发与 UI 组合。

### Context（RecordBoardContextValue）速查

| 分类   | 字段                                                                                | 说明                                                                                                |
|------|-----------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
| 列表   | `data`、`loading`、`total`                                                          | 当前页数据、加载态、总条数                                                                                     |
| 分页   | `pagination`、`onPageChange`                                                       | `{ pageNumber, pageSize }`、翻页回调                                                                   |
| 搜索   | `searchParams`、`onSearch`、`onReset`                                               | 含 `keyword`、`filter`、`sort` 等；`onSearch` 一般会重置到第一页（由业务实现）                                         |
| 布局   | `layoutType`                                                                      | `grid` / `card`                                                                                   |
| 多选   | `selectedKeys`、`selectedRows`、`onSelectionChange`、`rowKey`                        | 若 `grid` 传入受控多选，则与 context 合并为受控态                                                                 |
| 列 UI | `columnVisibility`、`onColumnVisibilityChange`、`columnOrder`、`onColumnOrderChange` | 容器内部状态；**不从 props 传入**                                                                            |
| 透传   | `childComponentProps`                                                             | 由根组件从打散 props 合并 `grid` / `toolBar` / … 注入                                                        |
| 高度   | `fillHeight`、`scrollAreaHeight`、`setScrollAreaHeight`                             | 默认 `fillHeight: true`；`scroll.autoCalc` 时 ShellFrame 测量内容区高度写回                                    |
| 筛选重置 | `filterResetKey`、`setFilterResetKey`                                              | 重置按钮递增 key，强制 PisellFilter 重挂载                                                                    |
| 双视图  | `bodyView`、`setBodyView`、`floorMapProps`                                          | 根级可选 `floorMap`；Shell 内 **`FloorMap` 与根级合并**，子优先；无平面图槽位时一般为 grid（仅 FloorMap 时由 Shell 置为 floorMap） |

子组件使用 **`PisellRecordBoard.useRecordBoardContext()`**；传入 **`{ displayName: 'grid' }`** 时，返回值会合并 *
*`childComponentProps.grid`**（便于 Grid 直接拿到 `columns`、`scroll` 等）。

### searchParams 约定（与 ToolBar 联动）

| 字段        | 含义                                             | 典型写入方                           |
|-----------|------------------------------------------------|---------------------------------|
| `keyword` | 关键词搜索                                          | PisellFind（ToolBar 内）           |
| `filter`  | 筛选结构：`{ quickFilter?, otherFilter?, values? }` | ToolBarQuickFilter、PisellFilter | 
| `sort`    | 排序，单值字符串                                       | Sort 组件                         |

- 左侧 **QuickFilter** 与 **`searchParams.filter.quickFilter`** 同步；右侧 **PisellFilter** 传入时会 **清空 quickFilter
  数组**（`hiddenQuickFilter: true`），避免与左侧重复。
- **筛选默认值**：在受控 `searchParams` 初始对象中带上 `filter`，在 `values` 里按筛项 `name`/`key` 赋默认值（见下文「筛选默认值」小节）。

### 列显隐与列顺序持久化

- 列显示隐藏由容器 **`useState`** 维护，初始值来自 **`grid.defaultHiddenColumnKeys`**、列上 **`hidden: true`**，并可与 *
  *localStorage** 合并。
- 传入 **`columnVisibilityStorageKey`**（字符串）时，使用 **`@pisell/utils`** 的列设置存储（`saveColumnVisibilityToLocal` /
  `getRecordBoardColumnSettingFromLocal`）：持久化 **显隐** 与 **列顺序**（ColumnSetting 拖拽）。低代码场景可传 **`__id`**
  作为存储 key（与 DataSourceContainer 规则一致）。
- **仅影响表格/卡片列展示**，不参与筛选/排序配置生成。

### 打散 props 与 childComponentProps

根组件 **`mergeChildComponentProps`** 将 `grid`、`toolBar`、`search`、`paginationConfig`、`batchActionBar`、`cardList` 合并为
Context 中的 **`childComponentProps`**，供 ShellFrame 内各模块读取。列表数据字段 **`data` / `pagination` / `searchParams`
等仍为根级 props**，勿放进 `childComponentProps`。

## 何时使用

- 列表页需要统一的搜索、筛选、排序、分页
- 希望用列配置自动生成筛选项与排序项，减少重复配置
- 需要表格 + 工具栏 + 多选操作栏 + 分页的完整布局
- 需要 Grid 与 Card 两种布局切换或单独使用

## 代码演示

### 基础用法（ShellFrame + GridLayout）

受控用法：由上层注入 `value`，子组件通过 Context 消费。

```tsx
import React, {useState} from 'react';
import {PisellRecordBoard} from '@pisell/materials';

const list = [{id: '1', orderNo: 'ORD001', status: 'enabled'}];
const columns = [
    {title: '订单号', dataIndex: 'orderNo', key: 'orderNo'},
    {title: '状态', dataIndex: 'status', key: 'status'},
];

export default () => {
    const [searchParams, setSearchParams] = useState<Record<string, unknown>>({});
    const value = {
        data: list,
        loading: false,
        total: 100,
        pagination: {pageNumber: 1, pageSize: 10},
        onPageChange: () => {
        },
        searchParams,
        onSearch: (params: Record<string, unknown>) => setSearchParams(params),
        layoutType: 'grid' as const,
        rowKey: 'id',
    };

    return (
        <PisellRecordBoard rowKey="id" {...value} grid={{columns}}>
            <PisellRecordBoard.ShellFrame>
                <PisellRecordBoard.GridLayout/>
            </PisellRecordBoard.ShellFrame>
        </PisellRecordBoard>
    );
};
```

列配置通过 `grid.columns` 传入；ToolBar 会根据 columns 自动派生筛选与排序。

### 列配置：有 type（自动派生）

传入 `type` 时，表格用对应 Pisell 组件只读渲染，筛选用编辑态，排序从组件 SortOptions 派生。

```tsx
import React, {useState} from 'react';
import {PisellRecordBoard} from '@pisell/materials';

const list = [{id: '1', orderNo: 'ORD001', status: 'enabled', createTime: '2024-01-01 12:00'}];
const columns = [
    {title: '订单号', dataIndex: 'orderNo', key: 'orderNo', width: 160, type: 'singleLineText'},
    {
        title: '状态',
        dataIndex: 'status',
        key: 'status',
        type: 'singleSelect',
        fieldProps: {
            options: [
                {label: '全部', value: 'all'},
                {label: '启用', value: 'enabled'},
                {label: '禁用', value: 'disabled'},
            ],
        },
    },
    {
        title: '下单时间',
        dataIndex: 'createTime',
        key: 'createTime',
        type: 'dateTime',
        fieldProps: {customFormat: 'YYYY-MM-DD HH:mm'},
        filterable: true,
        filter: {quickFilter: true},
    },
];

export default () => {
    const [searchParams, setSearchParams] = useState<Record<string, unknown>>({});
    const value = {
        data: list,
        loading: false,
        total: 1,
        pagination: {pageNumber: 1, pageSize: 10},
        onPageChange: () => {
        },
        searchParams,
        onSearch: (params: Record<string, unknown>) => setSearchParams(params),
        layoutType: 'grid' as const,
        rowKey: 'id',
    };
    return (
        <PisellRecordBoard
            rowKey="id"
            {...value}
            grid={{columns, scroll: {x: 1200}}}
            toolBar={{search: true, quickFilter: true, filter: {hiddenQuickFilter: true}}}
        >
            <PisellRecordBoard.ShellFrame>
                <PisellRecordBoard.GridLayout/>
            </PisellRecordBoard.ShellFrame>
        </PisellRecordBoard>
    );
};
```

### 列配置：无 type（自定义 render / filter / sort）

不传 `type` 时，表格无自定义 render 则纯文本；筛选需配置 `filterable` + `filter`（type 或 component）；排序需配置
`sortable` + `sort`。

```tsx
import React, {useState} from 'react';
import {PisellRecordBoard} from '@pisell/materials';

const list = [{id: '1', status: 'enabled'}];
const columns = [
    {
        title: '状态',
        dataIndex: 'status',
        key: 'status',
        render: (val: string) => (val === 'enabled' ? '启用' : '禁用'),
        filterable: true,
        filter: {
            type: 'single',
            options: [
                {label: '全部', value: 'all'},
                {label: '启用', value: 'enabled'},
                {label: '禁用', value: 'disabled'},
            ],
        },
        sortable: true,
        sort: {
            getSortItems: (key: string) => [
                {key: `${key}:asc`, label: 'A-Z'},
                {key: `${key}:desc`, label: 'Z-A'},
            ],
        },
    },
];

export default () => {
    const [searchParams, setSearchParams] = useState<Record<string, unknown>>({});
    const value = {
        data: list,
        loading: false,
        total: 1,
        pagination: {pageNumber: 1, pageSize: 10},
        onPageChange: () => {
        },
        searchParams,
        onSearch: (params: Record<string, unknown>) => setSearchParams(params),
        layoutType: 'grid' as const,
        rowKey: 'id',
    };
    const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
    const [selectedRows, setSelectedRows] = useState<any[]>([]);
    return (
        <PisellRecordBoard
            rowKey="id"
            {...value}
            grid={{
                columns,
                selectedKeys,
                selectedRows,
                onSelectionChange: (keys, rows) => {
                    setSelectedKeys(keys);
                    setSelectedRows(rows);
                },
            }}
        >
            <PisellRecordBoard.ShellFrame>
                <PisellRecordBoard.GridLayout/>
            </PisellRecordBoard.ShellFrame>
        </PisellRecordBoard>
    );
};
```

### 无 type + 自定义筛选组件

筛选可传入 `filter.component`，组件需实现 `value` 与 `onChange`，由 Form 注入。

```tsx
import React, {useState} from 'react';
import {Input} from 'antd';
import {PisellRecordBoard} from '@pisell/materials';

const MyFilterInput = ({
                           value,
                           onChange,
                       }: {
    value?: string;
    onChange?: (v: string) => void;
}) => (
    <Input
        {...value}
        onChange={(e) => onChange?.(e.target.value)}
        placeholder="自定义筛选"
    />
);

const list = [{id: '1', remark: '备注内容'}];
const columns = [
    {
        title: '备注',
        dataIndex: 'remark',
        key: 'remark',
        filterable: true,
        filter: {component: MyFilterInput},
    },
];

export default () => {
    const [searchParams, setSearchParams] = useState<Record<string, unknown>>({});
    const value = {
        data: list,
        loading: false,
        total: 1,
        pagination: {pageNumber: 1, pageSize: 10},
        onPageChange: () => {
        },
        searchParams,
        onSearch: (params: Record<string, unknown>) => setSearchParams(params),
        layoutType: 'grid' as const,
        rowKey: 'id',
    };
    const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
    const [selectedRows, setSelectedRows] = useState<any[]>([]);
    return (
        <PisellRecordBoard
            rowKey="id"
            {...value}
            grid={{
                columns,
                selectedKeys,
                selectedRows,
                onSelectionChange: (keys, rows) => {
                    setSelectedKeys(keys);
                    setSelectedRows(rows);
                },
            }}
        >
            <PisellRecordBoard.ShellFrame>
                <PisellRecordBoard.GridLayout/>
            </PisellRecordBoard.ShellFrame>
        </PisellRecordBoard>
    );
};
```

### 有 type 时覆盖（render / filter / sort）

有 type 时也可传入 `render`、`filter`、`sort`，优先使用传入配置，覆盖自动派生。

```tsx
import React, {useState} from 'react';
import {PisellRecordBoard} from '@pisell/materials';

const list = [{id: '1', amount: 99.5}];
const columns = [
    {
        title: '金额',
        dataIndex: 'amount',
        key: 'amount',
        type: 'currency',
        fieldProps: {precision: 2, currencySymbol: '¥'},
        render: (val: number) => <span style={{color: 'red'}}>¥{val}</span>,
    },
];

export default () => {
    const [searchParams, setSearchParams] = useState<Record<string, unknown>>({});
    const value = {
        data: list,
        loading: false,
        total: 1,
        pagination: {pageNumber: 1, pageSize: 10},
        onPageChange: () => {
        },
        searchParams,
        onSearch: (params: Record<string, unknown>) => setSearchParams(params),
        layoutType: 'grid' as const,
        rowKey: 'id',
    };
    const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
    const [selectedRows, setSelectedRows] = useState<any[]>([]);
    return (
        <PisellRecordBoard
            rowKey="id"
            {...value}
            grid={{
                columns,
                selectedKeys,
                selectedRows,
                onSelectionChange: (keys, rows) => {
                    setSelectedKeys(keys);
                    setSelectedRows(rows);
                },
            }}
        >
            <PisellRecordBoard.ShellFrame>
                <PisellRecordBoard.GridLayout/>
            </PisellRecordBoard.ShellFrame>
        </PisellRecordBoard>
    );
};
```

### 工具栏透传（搜索、QuickFilter、slots）

通过 `toolBar` 配置搜索、快速筛选、筛选弹层、排序，以及 `topLeft` / `topRight` 插槽。

```tsx
import React, {useState} from 'react';
import {Button} from 'antd';
import {PisellRecordBoard} from '@pisell/materials';

const list = [{id: '1', name: '示例'}];
const columns = [{title: '名称', dataIndex: 'name', key: 'name'}];

export default () => {
    const [searchParams, setSearchParams] = useState<Record<string, unknown>>({});
    const value = {
        data: list,
        loading: false,
        total: 1,
        pagination: {pageNumber: 1, pageSize: 10},
        onPageChange: () => {
        },
        searchParams,
        onSearch: (params: Record<string, unknown>) => setSearchParams(params),
        layoutType: 'grid' as const,
        rowKey: 'id',
    };

    return (
        <PisellRecordBoard
            rowKey="id"
            {...value}
            grid={{columns}}
            toolBar={{
                search: true,
                quickFilter: true,
                filter: {hiddenQuickFilter: true},
                sort: true,
                topLeft: <Button type="primary">新建</Button>,
            }}
        >
            <PisellRecordBoard.ShellFrame>
                <PisellRecordBoard.GridLayout/>
            </PisellRecordBoard.ShellFrame>
        </PisellRecordBoard>
    );
};
```

### 筛选默认值（QuickFilter）

左侧 QuickFilter 的表单展示和请求参数都来自 **`searchParams.filter.values`**。要带默认值（如订单日期默认「近 7 天」），在**受控
**使用时，用初始的 `searchParams` 把默认值写进 `filter.values` 即可。

- 传入 RecordBoard 的 `searchParams` 初始值里包含
  `filter: { values: { [字段名]: 默认值 }, quickFilter: [], otherFilter: [...] }`。
- `filter.values` 的 key 需与 quickFilter 的 `filterList` 里每一项的 `name`/`key` 一致（如 `orderDate`）。
- 日期范围等传 **与快筛控件一致的值类型**（如 `[start, end]` 字符串、Date 等）。快筛项类型 **`rangePickerNew`** 使用 antd
  `RangePicker`，**单独使用文档/示例时无需 MUI 上下文**；**`rangePicker`** 走自研日期范围（MUI X），应用根须包裹
  **`LocalizationProvider` + `AdapterDayjs`**（见 `@pisell/date-picker`）。

示例（`orderDate` 默认一段固定日期范围；使用 **`rangePickerNew`**，仅演示结构）：

```tsx
import React, {useState} from 'react';
import {PisellRecordBoard} from '@pisell/materials';

const list: { id: string }[] = [];

export default () => {
    const [searchParams, setSearchParams] = useState<Record<string, unknown>>({
        filter: {
            quickFilter: [],
            otherFilter: [],
            values: {
                orderDate: ['2024-01-01', '2024-01-08'],
            },
        },
    });

    const value = {
        data: list,
        loading: false,
        total: 0,
        pagination: {pageNumber: 1, pageSize: 10},
        onPageChange: () => {
        },
        searchParams,
        onSearch: (params: Record<string, unknown>) => setSearchParams(params),
        layoutType: 'grid' as const,
        rowKey: 'id',
    };

    return (
        <PisellRecordBoard
            rowKey="id"
            {...value}
            grid={{columns: []}}
            toolBar={{
                quickFilter: {
                    filterList: [
                        {
                            type: 'rangePickerNew',
                            name: 'orderDate',
                            key: 'orderDate',
                            label: '订单日期',
                            props: {placeholder: ['开始', '结束']},
                        },
                    ],
                },
                filter: {hiddenQuickFilter: true},
            }}
        >
            <PisellRecordBoard.ShellFrame>
                <PisellRecordBoard.GridLayout/>
            </PisellRecordBoard.ShellFrame>
        </PisellRecordBoard>
    );
};
```

这样首屏会显示默认日期范围，且首次请求会带上该筛选条件。

### 自定义组合（不用 ShellFrame）

可单独使用 ToolBar、BatchActionBar、Grid、Pagination 自由组合。

```tsx
import React, {useState} from 'react';
import {PisellRecordBoard} from '@pisell/materials';

const list = [{id: '1', name: '示例'}];
const columns = [{title: '名称', dataIndex: 'name', key: 'name'}];

export default () => {
    const [searchParams, setSearchParams] = useState<Record<string, unknown>>({});
    const value = {
        data: list,
        loading: false,
        total: 1,
        pagination: {pageNumber: 1, pageSize: 10},
        onPageChange: () => {
        },
        searchParams,
        onSearch: (params: Record<string, unknown>) => setSearchParams(params),
        layoutType: 'grid' as const,
        rowKey: 'id',
    };

    return (
        <PisellRecordBoard rowKey="id" {...value} grid={{columns}}>
            <PisellRecordBoard.ToolBar/>
            <PisellRecordBoard.BatchActionBar/>
            <PisellRecordBoard.Grid/>
            <PisellRecordBoard.Pagination/>
        </PisellRecordBoard>
    );
};
```

### 同值列折叠（grid.collapseUniformColumns）

该功能**默认开启**。当 `grid.collapseUniformColumns` 不为 `false` 时，对**当前页**数据做检测：若某列在所有行上的值**全部相同
**或**全部为空**，则该列从表格中隐藏，并在表格上方展示一行**总结区**，展示「列名 + 统一值」或「列名 + 空数据」。

- **比较规则**：用于比较的值来自列 **uniformCompare**（若有）或 dataIndex。**uniformCompare** 为函数
  `(value, record) => string | number | null | undefined`：传入当前格子的 value 与整行 record，返回用于比较的值；返回
  string/number 时参与全等比较，返回 null/undefined 则该列不参与折叠。未配置 uniformCompare 时用 dataIndex 取值；仅当取值为
  string/number 时才参与比较。
- **列配置**：列上可设置 **uniformCompare**（类似 render 的 (value, record) 函数）、**collapseWhenUniform**（默认 `true`，为
  `false` 时该列不参与折叠）。
- **总结区展示**：优先使用列的 **render(value, record, index)** 渲染统一值/空占位；若列未配置 render，则直接展示原始值或「-」。
- 总结区样式参考 Figma（灰底、列名 + Badge/空占位），仅当存在至少一项折叠列时渲染。
- **关闭**：传 `grid={{ collapseUniformColumns: false }}` 即可关闭该功能。

```
// 默认开启，无需配置；若需关闭：
grid = {
{
    columns,
        collapseUniformColumns
:
    false,
}
}
```

### grid 透传 antd Table props

`grid` 类型为 **RecordBoardGridProps**
，在自有字段（columns、scroll、size、selectedKeys、selectedRows、onSelectionChange、collapseUniformColumns）基础上，与 antd *
*TableProps** 做交叉类型并省略由 context 或组件自管的项（dataSource、loading、pagination、columns、scroll、size、rowSelection），因此支持
Table 其余 props 透传，如 `onRow`、`onHeaderRow`、`expandable`、`rowClassName`、`summary` 等。

### 自定义表格行点击（grid.onRow.onClick）

通过 `grid.onRow` 传入行属性，在 `onClick` 中处理行点击（如跳转详情、打开抽屉等）。下例为自包含示例，点击表格行会弹出当前行数据。

```tsx
import React, {useState} from 'react';
import {PisellRecordBoard} from '@pisell/materials';

type RowRecord = { id: string; orderNo: string; status: string; amount: number };

const list: RowRecord[] = [
    {id: '1', orderNo: 'ORD001', status: '已支付', amount: 99.9},
    {id: '2', orderNo: 'ORD002', status: '待发货', amount: 199},
    {id: '3', orderNo: 'ORD003', status: '已取消', amount: 0},
];

const columns = [
    {title: '订单号', dataIndex: 'orderNo', key: 'orderNo'},
    {title: '状态', dataIndex: 'status', key: 'status'},
    {title: '金额', dataIndex: 'amount', key: 'amount'},
];

export default function TableOnRowClickExample() {
    const [searchParams, setSearchParams] = useState<Record<string, unknown>>({});
    const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
    const [selectedRows, setSelectedRows] = useState<RowRecord[]>([]);

    const value = {
        data: list,
        loading: false,
        total: list.length,
        pagination: {pageNumber: 1, pageSize: 10},
        onPageChange: () => {
        },
        searchParams,
        onSearch: (params: Record<string, unknown>) => setSearchParams(params),
        onReset: () => setSearchParams({}),
        layoutType: 'grid' as const,
        rowKey: 'id',
    };

    return (
        <PisellRecordBoard rowKey="id" {...value} grid={{
            columns,
            selectedKeys,
            selectedRows,
            onSelectionChange: (keys, rows) => {
                setSelectedKeys(keys);
                setSelectedRows(rows as RowRecord[]);
            },
            onRow: (record: RowRecord) => ({
                onClick: () => {
                    // 自定义行点击：如跳转详情、打开抽屉等
                    console.log('行点击', record);
                    alert(`点击订单：${record.orderNo}，状态：${record.status}，金额：¥${record.amount}`);
                },
            }),
        }}>
            <PisellRecordBoard.ShellFrame>
                <PisellRecordBoard.GridLayout/>
            </PisellRecordBoard.ShellFrame>
        </PisellRecordBoard>
    );
}
```

## 平面图（Floor Map）

**推荐用法**：在 **`PisellRecordBoard.ShellFrame`** 内与 **`GridLayout`（或 `CardLayout`）并列** 放置 *
*`PisellRecordBoard.FloorMap`**，把与 **PisellFloorMapLayout** 对齐的 props 写在 `FloorMap` 上（类型为
`Partial<PisellFloorMapLayoutProps>` 的 `RecordBoardFloorMapProps`）。**传什么用什么**：

| ShellFrame 子节点组合                 | 行为                                                                  |
|----------------------------------|---------------------------------------------------------------------|
| 仅 `GridLayout` / `CardLayout`    | 仅表格（或卡片）区，**无**平面图切换                                                |
| 仅 `FloorMap`                     | 仅平面图，**无** Segmented，无底部分页                                          |
| `GridLayout`（或 Card）+ `FloorMap` | **双视图**：ToolBar 最左侧 **Segmented**「表格 \| 平面图」；`defaultBodyView` 控制首屏 |

**兼容**：根级仍可传 **`floorMap`**（如 **PisellSalesGrid**、低代码透传）；若同时存在 **`FloorMap` 子组件**，则 *
*`{...根级 floorMap, ...FloorMap.props}`**，**子 props 覆盖根级**。

- **`defaultBodyView`**：`grid`（默认）或 `floorMap`，仅在 **双视图** 时影响首屏。

- **`floorMapLayoutContext`**（根级，类型为 **`FloorMapLayoutContextValue`**，与 `@pisell/materials` 中平面图 Context 同形）：把
  **`floorMapConfig`**、**`onSave`**、**`onDataSourceRecordSave`**、**`loading` / `saveError`**、*
  *`dataSourceLabels` / `dataSourceFormColumns` / `dataSourceRecordForm`** 等注入 Shell *
  *内置的 `FloorMapLayoutProvider`**，**`floorMap` / `FloorMap` 子组件可只保留展示向 props**（如 `renderItemByKind`、
  `mapLayer`、`mode`、`getMergedItemWrapperStyle`）。**与表格共用的 `dataSources` 仍应走根级或 `floorMap.dataSources`**
  ：Shell 合并后以 props 传入 Layout，会**覆盖** Context 中的 `dataSources`。*
  *`useRecordBoardContext().floorMapLayoutContext`** 与注入 Provider 的 value **同源**，供 ToolBar 等（不在平面图 DOM
  子树内）读取。

**RecordBoard 层行为**（与 PisellFloorMapLayout 内部编辑能力正交，仅组合方式）：

1. **ShellFrame**：解析子节点，抽出 `FloorMap` 的 props，与根级 `floorMap` 合并；`bodyView === 'grid'` 时为滚动区 + **非
   FloorMap** 子树；`bodyView === 'floorMap'` 时用 **`FloorMapLayoutProvider`**（value 为根级 **`floorMapLayoutContext`**
   或稳定空对象）包裹 **PisellFloorMapLayout**（不包 ScrollAreaMeasurer）。`items` /
   `renderItem` 未传时仍补默认（空列表 / 空渲染）。
2. **ToolBar**：仅当 **同时存在「表格区子节点」与「平面图槽位」** 时渲染 **Segmented**；否则不显示切换器。
3. **分页**：仅在 **表格** 子视图展示；**平面图模式无底部分页**。

### 仅平面图：左侧快筛与搜索（无 GridLayout）

ShellFrame 内 **只放 `FloorMap`、不渲染 `GridLayout` / `CardLayout`** 时，ToolBar **仍会**展示 **QuickFilter**（若配置开启）与
**PisellFind 搜索**；筛值、关键词仍落在 **`searchParams.filter.values`**、**`searchParams.keyword`**，通过 **`onSearch`**
受控更新。**平面图上的高亮、压暗、按条件过滤展示**不会自动绑定表格逻辑，需业务侧根据 `searchParams` 过滤数据或透传 *
*`getMergedItemWrapperStyle`** 等自行实现（见 PisellFloorMapLayout 文档）。

配置左侧快筛有两种常见方式：

| 方式       | 做法                                                                                    | 说明                                                                                                                                                                                                                                          |
|----------|---------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **列派生**  | 根级照常传 **`grid={{ columns }}`**，列上配置 **`filter: { quickFilter: true, … }`**（与其它表格用法一致） | ToolBar 通过 **`deriveFilterFromColumns`** 从 `grid.columns` 生成快筛；**不要求** Shell 内存在 Grid 子节点。不需要「列设置」入口时可设 **`toolBar.columnSetting: false`**。                                                                                                 |
| **手写快筛** | **`toolBar.quickFilter: { filterList: [...] }`**                                      | `filterList` 项类型为 **`FilterItemProps`**（如 `packages/materials/src/components/filter/types.ts`），例如 `type: 'single'` + `props.options`。配置了 **`filterList`** 时通常 **优先于** 列派生（与 ToolBar 内逻辑一致）。也可写 **`toolBar.quickFilter: true`** 仅启用入口并依赖列派生。 |

搜索行为与表格模式相同：**`toolBar.search`** 控制是否展示及透传 PisellFind。

---

### 数据契约：表格行 ⇄ 平面图落位

| 概念                               | 约定                                     | 说明                                                                                                                                  |
|----------------------------------|----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| 数据源 key                          | 根级或 `FloorMap` 上合并后的 `dataSources` 的键  | 如 `tables`、`assets`，与 `floorMapConfig` 里图元 `dataSourceKey`、落位 `dataSourceKey` 一致                                                    |
| 行主键                              | 表格 `rowKey`（多为 `id`）                   | 平面图 **`sceneElements[].dataBinding.recordId`** 与表格行 id 一致时，图元与表格行一一对应                                                               |
| 配置体                              | `floorMapConfig`（`FloorMapViewConfig`） | `canvases`、`canvasUi`（`canCreate`/`canEdit`）、`sceneElements`、`elementKinds`、`elementKindCategories` 等；详细字段见 PisellFloorMapLayout 文档 |
| `elementKinds` 中 `dataSourceKey` | 可选                                     | 声明该图元从哪个数据源选行再落点                                                                                                                    |
| `renderItemByKind`               | 按 `elementKind` 渲染卡片                   | 使用 `dataSources` 里对应行数据渲染只读/编辑卡片；可与 Story 中 `getRenderItemByKindTable` 同类                                                           |

**易错点：`RecordBoard` 的 `data`、`dataSources` 与 `gridDataSourceKey`**

- **`data`**：显式传入时，**始终**作为表格数据源（常见为服务端分页的「当前页」）。
- **根级 `dataSources` + `gridDataSourceKey`**：当 **不传 `data`** 时，容器从 **`dataSources[gridDataSourceKey]`** 取行，并按
  **`pagination` 做客户端切片** 写入 Context 的 `data`；**`total`** 未传时默认为该数组长度。
- **`dataSources`（整份多 key）**：供 **平面图** 使用。ShellFrame 计算 *
  *`mergedDataSources = { ...ctx.dataSources, ...floorMap.dataSources }`**（后者覆盖同 key）再传入 **PisellFloorMapLayout
  **；**表格分页切片不得缩短此合并结果**，避免多源编辑/落位缺行。
- 若 ToolBar **筛选/排序** 应对表格与平面图同源：把 **`filteredSortedRows`** 写入对应 key（如 **`dataSources.tables`**
  ），或继续用显式 **`data` + 全量 `dataSources`** 自行对齐语义。

---

### `floorMap` 常用字段速查（与 RecordBoard 组合时）

| 字段                                                  | 作用                                                                            |
|-----------------------------------------------------|-------------------------------------------------------------------------------|
| `floorMapConfig`                                    | 画布、图元类型、落位坐标与尺寸、装饰物等                                                          |
| `dataSources`                                       | `Record<dataSourceKey, Array< { id: string, … }>>`，行 `id` 与 placement `id` 对齐 |
| `mode`                                              | `'read'` 只浏览；`'edit'` 可拖放、改落位、侧栏改记录（视 FloorMap 能力）                            |
| `onSave`                                            | 编辑后保存 **整份** `FloorMapViewConfig`（布局持久化）                                      |
| `onDataSourceRecordSave`                            | 侧栏保存 **单行** `(dataSourceKey, id, patch)`，合并回业务列表                              |
| `dataSourceFormColumns` / `dataSourceRecordForm`    | 侧栏「数据源记录」用内置列表单或自定义 React 表单                                                  |
| `dataSourceLabels`                                  | 数据源展示名                                                                        |
| `renderItemByKind`                                  | 按 kind 渲染占位上的卡片（需与 `floorMapConfig.elementKinds` 的 `value` 对齐）                |
| `mapLayer` / `zoom`(`pixel` 下) / `pan` / `controls` | 底图与视口交互，同 PisellFloorMapLayout                                                |

更完整的类型与编辑态说明见 **`pisellFloorMapLayout`** 包内 `docs/`。

---

### 示例一：只读平面图 + 表格分页（同源全量 `dataSources`）

下列示例自包含：**同一批 `rows`** 全量进 `dataSources`，表格只取当前页切片；筛选未演示，避免与上一节「全量/分页」混写。
`renderItemByKind` 用简单占位，实际项目可换成与设计稿一致的卡片组件。

```tsx
import React, {useMemo, useState} from 'react';
import {PisellRecordBoard} from '@pisell/materials';
import type {RecordBoardColumnType} from '@pisell/materials';

const DS_KEY = 'desks';

type DeskRow = { id: string; name: string; status: string };

const allRows: DeskRow[] = [
    {id: 'd1', name: 'A1', status: '空'},
    {id: 'd2', name: 'A2', status: '用'},
];

/** dataBinding.recordId 与行 id 一致 */
function buildFloorMapConfig() {
    return {
        canvases: [{id: 'main', name: '大厅', order: 0, enabled: true}],
        canvasUi: {canCreate: false, canEdit: false},
        elementKinds: [
            {
                value: 'table',
                label: '桌位',
                dataBindingPolicy: 'optional' as const,
                allowedDataSourceKeys: [DS_KEY],
                showInPalette: false,
            },
        ],
        sceneElements: [
            {
                instanceId: 'elm_d1',
                canvasId: 'main',
                x: 64,
                y: 64,
                name: 'A1',
                width: 120,
                height: 80,
                elementKind: 'table',
                dataBinding: {dataSourceKey: DS_KEY, recordId: 'd1'},
            },
            {
                instanceId: 'elm_d2',
                canvasId: 'main',
                x: 220,
                y: 64,
                name: 'A2',
                width: 120,
                height: 80,
                elementKind: 'table',
                dataBinding: {dataSourceKey: DS_KEY, recordId: 'd2'},
            },
        ],
    };
}

const columns: RecordBoardColumnType[] = [
    {title: '名称', dataIndex: 'name', key: 'name'},
    {title: '状态', dataIndex: 'status', key: 'status'},
];

export default function RecordBoardFloorMapReadonlyExample() {
    const [pageNumber, setPageNumber] = useState(1);
    const pageSize = 1;
    const [searchParams, setSearchParams] = useState<Record<string, unknown>>({});

    const total = allRows.length;
    const data = useMemo(() => {
        const start = (pageNumber - 1) * pageSize;
        return allRows.slice(start, start + pageSize);
    }, [pageNumber, pageSize]);

    const dataSources = useMemo(
        () => ({[DS_KEY]: allRows}),
        []
    );

    const [floorMapConfig, setFloorMapConfig] = useState(buildFloorMapConfig);

    const floorMap = useMemo(
        () => ({
            floorMapConfig,
            dataSources,
            mode: 'read' as const,
            onSave: setFloorMapConfig,
            renderItemByKind: {
                table: (item: { name?: string; status?: string }) => (
                    <div style={{padding: 6, fontSize: 12}}>
                        <div>{item.name}</div>
                        <div style={{color: '#888'}}>{item.status}</div>
                    </div>
                ),
            },
            mapLayer: {backgroundType: 'color' as const, backgroundColor: '#f0f0f0', padding: 16},
            zoom: {defaultScale: 1, min: 0.5, max: 2},
            pan: {enabled: true, boundary: 'clamp' as const},
        }),
        [floorMapConfig, dataSources]
    );

    return (
        <div style={{height: 560}}>
            <PisellRecordBoard
                rowKey="id"
                data={data}
                loading={false}
                total={total}
                pagination={{pageNumber, pageSize}}
                onPageChange={(n) => {
                    setPageNumber(n);
                }}
                searchParams={searchParams}
                onSearch={setSearchParams}
                defaultBodyView="grid"
                grid={{columns, scroll: {x: 600}}}
                toolBar={{search: false, filter: false, sort: false}}
            >
                <PisellRecordBoard.ShellFrame>
                    <PisellRecordBoard.GridLayout/>
                    <PisellRecordBoard.FloorMap {...floorMap} />
                </PisellRecordBoard.ShellFrame>
            </PisellRecordBoard>
        </div>
    );
}
```

---

### 示例二：编辑模式 + 布局保存 + 侧栏行数据写回

与 Storybook **Default** 同一思路：**`onSave`** 更新 `floorMapConfig`；**`onDataSourceRecordSave`** 把侧栏补丁合并进 *
*`rows` state**（表格与 `dataSources` 都引用该 state）。`mode` 在 `'read'` / `'edit'` 间切换即可。

```tsx
import React, {useMemo, useState, useCallback} from 'react';
import {PisellRecordBoard} from '@pisell/materials';
import type {RecordBoardColumnType} from '@pisell/materials';

const DS_KEY = 'tables';

type Row = { id: string; name: string; status: string; capacity: number };

const columns: RecordBoardColumnType[] = [
    {title: '名称', dataIndex: 'name', key: 'name', type: 'singleLineText'},
    {
        title: '状态',
        dataIndex: 'status',
        key: 'status',
        type: 'singleSelect',
        fieldProps: {
            options: [
                {label: '空', value: '空'},
                {label: '用', value: '用'},
            ],
        },
    },
    {title: '人数', dataIndex: 'capacity', key: 'capacity', type: 'number'},
];

function buildConfig(): Record<string, unknown> {
    return {
        canvases: [{id: 'main', name: '大厅', order: 0, enabled: true}],
        canvasUi: {canCreate: true, canEdit: true},
        elementKinds: [
            {
                value: 'table',
                label: '桌',
                dataBindingPolicy: 'optional' as const,
                allowedDataSourceKeys: [DS_KEY],
                showInPalette: true,
            },
        ],
        sceneElements: [
            {
                instanceId: 'elm_t1',
                canvasId: 'main',
                x: 80,
                y: 80,
                name: '1号',
                width: 140,
                height: 90,
                elementKind: 'table',
                dataBinding: {dataSourceKey: DS_KEY, recordId: 't1'},
            },
        ],
    };
}

export default function RecordBoardFloorMapEditExample() {
    const [rows, setRows] = useState<Row[]>([
        {id: 't1', name: '1号', status: '空', capacity: 4},
    ]);
    const [floorMapConfig, setFloorMapConfig] = useState(buildConfig);
    /** 可用 `<Switch checked={mapEdit} onChange={setMapEdit} />` 切换只读 / 编辑 */
    const [mapEdit, setMapEdit] = useState(true);
    const [pageNumber, setPageNumber] = useState(1);
    const pageSize = 10;
    const [searchParams, setSearchParams] = useState<Record<string, unknown>>({});

    const total = rows.length;
    const data = useMemo(() => {
        const start = (pageNumber - 1) * pageSize;
        return rows.slice(start, start + pageSize);
    }, [rows, pageNumber, pageSize]);

    const dataSources = useMemo(() => ({[DS_KEY]: rows}), [rows]);

    const handleRecordSave = useCallback((key: string, id: string, patch: Record<string, unknown>) => {
        if (key !== DS_KEY) return;
        setRows((prev) => prev.map((r) => (r.id === id ? {...r, ...patch} : r)));
    }, []);

    const floorMap = useMemo(
        () => ({
            floorMapConfig,
            dataSources,
            mode: (mapEdit ? 'edit' : 'read') as 'read' | 'edit',
            onSave: (cfg: Record<string, unknown>) => setFloorMapConfig(cfg),
            onDataSourceRecordSave: handleRecordSave,
            dataSourceLabels: {[DS_KEY]: '餐桌'},
            dataSourceFormColumns: {
                [DS_KEY]: [
                    {key: 'name', label: '名称', type: 'string' as const},
                    {
                        key: 'status',
                        label: '状态',
                        type: 'select' as const,
                        options: [
                            {label: '空', value: '空'},
                            {label: '用', value: '用'},
                        ],
                    },
                    {key: 'capacity', label: '人数', type: 'number' as const},
                ],
            },
            renderItemByKind: {
                table: (item: Row) => (
                    <div style={{padding: 6, fontSize: 12}}>
                        {item.name} · {item.status}
                    </div>
                ),
            },
            mapLayer: {backgroundType: 'color' as const, backgroundColor: '#f5f5f5', padding: 16},
            zoom: {defaultScale: 1, min: 0.4, max: 2},
            pan: {enabled: true, boundary: 'clamp' as const},
        }),
        [floorMapConfig, dataSources, mapEdit, handleRecordSave]
    );

    return (
        <div style={{height: 620}}>
            <PisellRecordBoard
                rowKey="id"
                data={data}
                loading={false}
                total={total}
                pagination={{pageNumber, pageSize}}
                onPageChange={(n, s) => {
                    setPageNumber(n);
                }}
                searchParams={searchParams}
                onSearch={(p) => {
                    setSearchParams(p);
                    setPageNumber(1);
                }}
                defaultBodyView="grid"
                grid={{columns, scroll: {x: 720}}}
                toolBar={{filter: false, sort: false}}
            >
                <PisellRecordBoard.ShellFrame>
                    <PisellRecordBoard.GridLayout/>
                    <PisellRecordBoard.FloorMap {...floorMap} />
                </PisellRecordBoard.ShellFrame>
            </PisellRecordBoard>
        </div>
    );
}
```

**自定义侧栏表单**：在 **`FloorMap` 的 props**（或与根级合并后的有效 props）上设置
`dataSourceRecordForm: { [DS_KEY]: (ctx) => <YourForm ctx={ctx} /> }`，在表单提交里调用 `ctx.onSaveRecord?.(values)`
；关闭该配置则走 `dataSourceFormColumns` 内置表单。完整类型见 PisellFloorMapLayout 的 `FloorMapRecordFormRenderContext`。

---

### 示例三：首屏打开平面图

与 **示例一** 同源数据与 `floorMap` 结构，仅将 **`defaultBodyView` 设为 `"floorMap"`**，首屏即平面图；仍可通过 ToolBar *
*Segmented** 切回表格。

```tsx
import React, { useMemo, useState } from 'react';
import { PisellRecordBoard } from '@pisell/materials';
import type { RecordBoardColumnType } from '@pisell/materials';

const DS_KEY = 'desks';

type DeskRow = { id: string; name: string; status: string };

const allRows: DeskRow[] = [
  { id: 'd1', name: 'A1', status: '空' },
  { id: 'd2', name: 'A2', status: '用' },
];

function buildFloorMapConfig() {
  return {
    canvases: [{ id: 'main', name: '大厅', order: 0, enabled: true }],
    canvasUi: { canCreate: false, canEdit: false },
    elementKinds: [
      {
        value: 'table',
        label: '桌位',
        dataBindingPolicy: 'optional' as const,
        allowedDataSourceKeys: [DS_KEY],
        showInPalette: false,
      },
    ],
    sceneElements: [
      {
        instanceId: 'elm_d1',
        canvasId: 'main',
        x: 64,
        y: 64,
        name: 'A1',
        width: 120,
        height: 80,
        elementKind: 'table',
        dataBinding: { dataSourceKey: DS_KEY, recordId: 'd1' },
      },
      {
        instanceId: 'elm_d2',
        canvasId: 'main',
        x: 220,
        y: 64,
        name: 'A2',
        width: 120,
        height: 80,
        elementKind: 'table',
        dataBinding: { dataSourceKey: DS_KEY, recordId: 'd2' },
      },
    ],
  };
}

const columns: RecordBoardColumnType[] = [
  { title: '名称', dataIndex: 'name', key: 'name' },
  { title: '状态', dataIndex: 'status', key: 'status' },
];

export default function RecordBoardFloorMapDefaultBodyExample() {
  const [pageNumber, setPageNumber] = useState(1);
  const pageSize = 1;
  const [searchParams, setSearchParams] = useState<Record<string, unknown>>({});

  const total = allRows.length;
  const data = useMemo(() => {
    const start = (pageNumber - 1) * pageSize;
    return allRows.slice(start, start + pageSize);
  }, [pageNumber, pageSize]);

  const dataSources = useMemo(() => ({ [DS_KEY]: allRows }), []);

  const [floorMapConfig, setFloorMapConfig] = useState(buildFloorMapConfig);

  const floorMap = useMemo(
    () => ({
      floorMapConfig,
      dataSources,
      mode: 'read' as const,
      onSave: setFloorMapConfig,
      renderItemByKind: {
        table: (item: { name?: string; status?: string }) => (
          <div style={{ padding: 6, fontSize: 12 }}>
            <div>{item.name}</div>
            <div style={{ color: '#888' }}>{item.status}</div>
          </div>
        ),
      },
      mapLayer: {
        backgroundType: 'color' as const,
        backgroundColor: '#f0f0f0',
        padding: 16,
      },
      zoom: { defaultScale: 1, min: 0.5, max: 2 },
      pan: { enabled: true, boundary: 'clamp' as const },
    }),
    [floorMapConfig, dataSources]
  );

  return (
    <div style={{ height: 560 }}>
      <PisellRecordBoard
        rowKey="id"
        data={data}
        loading={false}
        total={total}
        pagination={{ pageNumber, pageSize }}
        onPageChange={(n) => setPageNumber(n)}
        searchParams={searchParams}
        onSearch={setSearchParams}
        defaultBodyView="floorMap"
        grid={{ columns, scroll: { x: 600 } }}
        toolBar={{ search: false, filter: false, sort: false }}
      >
        <PisellRecordBoard.ShellFrame>
          <PisellRecordBoard.GridLayout />
          <PisellRecordBoard.FloorMap {...floorMap} />
        </PisellRecordBoard.ShellFrame>
      </PisellRecordBoard>
    </div>
  );
}
```

---

### 与 PisellFloorMapLayout 文档的关系

平面图编辑态、`floorMapConfig` 各字段、`pixel` 坐标、`onSave` / `onDataSourceRecordSave`、`dataSourceFormColumns` /
`dataSourceRecordForm`、多画布 Tab 等，以 **`pisellFloorMapLayout` 使用文档与开发文档** 为准。RecordBoard 只叠加：**工具栏视图切换
**、**表格子树与平面图互斥主内容**、**表格模式下底部分页**。

完整交互与「餐厅餐桌」数据（含 `getRenderItemByKindTable`、筛选与排序联动）见 **Storybook → `PisellRecordBoard` → Default**（
`PisellRecordBoard.stories.tsx`）。

## Storybook 一览

| Story                                                               | 说明                                                                                                                                                           |
|---------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Default**                                                         | **多源平面图示例**：根级 `dataSources`（餐桌 + 蘸料台）+ **`gridDataSourceKey="tables"`**（不传 `data`，容器客户端分页）；`onSave` / `onDataSourceRecordSave`；侧栏表单与 `getRenderItemByKind*` |
| GridLayoutWithTable / GridLayoutWithColumnType                      | 基础表格与列 `type` 自动渲染                                                                                                                                           |
| GridWithMultiplePages                                               | 多页分页                                                                                                                                                         |
| GridLayoutWithCollapseUniformColumns / GridLayoutWithUniformCompare | 同值列折叠与 `uniformCompare`                                                                                                                                      |
| GridWithOnRow                                                       | `grid.onRow` 行点击                                                                                                                                             |
| GridLayoutToolBarSortAndFilter                                      | 工具栏排序与筛选                                                                                                                                                     |
| GridLayoutWithQuickFilterOnLeft / CustomQuickFilterAndColumnsRender | 左侧快筛与自定义快筛列表                                                                                                                                                 |
| CustomFilterSortRender                                              | 自定义 filter/sort/render                                                                                                                                       |
| GridLayoutWithPisellFindInToolBar / GridLayoutToolBarSearchInput    | 搜索形态                                                                                                                                                         |
| GridLayoutFull                                                      | 完整工具栏组合                                                                                                                                                      |
| Placeholder / CardLayoutPlaceholder                                 | 占位                                                                                                                                                           |
| CustomComposition / WithPisellFindSearch / SearchAndGridOnly        | 无 ShellFrame 的拼装                                                                                                                                             |

源码：`PisellRecordBoard.stories.tsx`。

## 类型导出（`@pisell/materials`）

除默认导出的 **`PisellRecordBoard`** 外，可从包内命名导出：

`RecordBoardProps`、`RecordBoardLayoutType`、`RecordBoardContextValue`、`RecordBoardChildComponentProps`、
`RecordBoardGridProps`、`RecordBoardColumnType`、`RecordBoardFieldType`、`RecordBoardColumnFilterConfig`、
`RecordBoardOptionItem`、`RecordBoardGetOptions`、`RecordBoardBodyView`、`RecordBoardFloorMapProps`、
`useRecordBoardContext`。

## API

### PisellRecordBoard Props

| 属性                                                                                     | 类型                                                       | 说明                                                                                                                                                                                                                                                                |
|----------------------------------------------------------------------------------------|----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| rowKey                                                                                 | string \| (record) => Key                                | 行主键                                                                                                                                                                                                                                                               |
| layoutType                                                                             | 'grid' \| 'card'                                         | 布局类型                                                                                                                                                                                                                                                              |
| data / loading / total / pagination / onPageChange / searchParams / onSearch / onReset | 各自类型                                                     | 打散的数据与回调                                                                                                                                                                                                                                                          |
| dataSources                                                                            | `FloorMapDataSources`（`Record<key, Array<{ id, ... }>>`） | **可选**。根级多路字典，进 Context；与 `FloorMap`/根级 `floorMap` 的 `dataSources` 在 ShellFrame 内合并后给平面图。**不传 `data` 时**可配合 `gridDataSourceKey` 推导表格行（客户端分页切片）                                                                                                                    |
| gridDataSourceKey                                                                      | `string`                                                 | **可选**。表格绑定的 `dataSources` key；**仅当根级未传 `data`** 时生效                                                                                                                                                                                                              |
| columnVisibilityStorageKey                                                             | `string`                                                 | 可选；列显隐 + 列顺序持久化存储 key；未传时也可用低代码 `__id`                                                                                                                                                                                                                            |
| floorMap                                                                               | RecordBoardFloorMapProps                                 | **可选、兼容透传**（如 SalesGrid）。推荐用 Shell 内 **`FloorMap`**；若同时存在，与子组件 props **合并，子优先**。仅根级有平面图而无 Grid 时也会出现平面图区                                                                                                                                                          |
| defaultBodyView                                                                        | `'grid'` \| `'floorMap'`                                 | **双视图**（Grid/Card + `FloorMap`）时首屏子视图，默认 `'grid'`                                                                                                                                                                                                                 |
| grid / toolBar / search / paginationConfig / batchActionBar / cardList                 | 各自类型                                                     | 打散的子组件配置。**grid** 类型为 RecordBoardGridProps（基于 TableProps 省略 dataSource/loading/pagination/columns/scroll/size/rowSelection），多选在 grid 内；支持 **collapseUniformColumns**（当前页某列全同值/全空时隐藏该列并在表格上方展示总结区）；支持 Table 透传：onRow、onHeaderRow、expandable、rowClassName、summary 等 |

默认占满父容器高度（父容器需有高度，如 `height: 100vh` 或 `height: 100%`），内容区可滚动，分页器贴底（**平面图模式下分页不展示
**）。

### 子组件

| 子组件                                     | 说明                                                                                                                                              |
|-----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
| PisellRecordBoard.ShellFrame            | 外壳：解析 GridLayout / CardLayout / **FloorMap** 子节点，合并平面图 props（子优先），渲染主内容与分页；双视图时 ToolBar 出现表格·平面图切换；切到平面图时不包裹 ScrollAreaMeasurer；**分页仅在表格子视图显示** |
| PisellRecordBoard.GridLayout            | 表格布局                                                                                                                                            |
| PisellRecordBoard.CardLayout            | 卡片布局                                                                                                                                            |
| PisellRecordBoard.FloorMap              | 平面图槽位（无 DOM），props 对齐 PisellFloorMapLayout；仅在 ShellFrame 内使用；与 Grid/Card 并存时出现 Segmented                                                        |
| PisellRecordBoard.ToolBar               | 工具栏（含搜索、QuickFilter、Sort、Filter、列设置、Reset、slots）                                                                                                |
| PisellRecordBoard.Search                | 搜索槽                                                                                                                                             |
| PisellRecordBoard.Pagination            | 分页槽                                                                                                                                             |
| PisellRecordBoard.Grid                  | 表格视图（支持 columnVisibility 列显示隐藏）                                                                                                                 |
| PisellRecordBoard.CardList              | 卡片列表视图                                                                                                                                          |
| PisellRecordBoard.BatchActionBar        | 多选操作栏                                                                                                                                           |
| PisellRecordBoard.useRecordBoardContext | 消费上下文的 Hook                                                                                                                                     |

### 列配置（RecordBoardColumnType）

与 antd Table Column 兼容，额外支持：

| 属性                  | 类型                                                       | 说明                                                                                                                                         |
|---------------------|----------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
| type                | RecordBoardFieldType                                     | 字段类型，有则表格/筛选/排序自动派生                                                                                                                        |
| fieldProps          | object                                                   | 传给 Pisell 组件的 props；singleSelect/multipleSelect 支持 `options` 或 `getOptions`（同步/异步返回选项）                                                     |
| filterable          | boolean                                                  | 是否参与筛选                                                                                                                                     |
| filter              | RecordBoardColumnFilterConfig                            | 筛选配置（type 或 component）；type 为 single 时支持 `options` 或 `getOptions`                                                                          |
| sortable            | boolean                                                  | 是否参与排序                                                                                                                                     |
| sort                | { type?, getSortItems? }                                 | 排序配置                                                                                                                                       |
| uniformCompare      | (value, record) => string \| number \| null \| undefined | 当 grid.collapseUniformColumns 为 true 时，用此函数从 (value, record) 得到用于比较的值；返回 string/number 参与全等比较，返回 null/undefined 则该列不参与折叠。未传则用 dataIndex 取值 |
| collapseWhenUniform | boolean                                                  | 当 grid.collapseUniformColumns 为 true 时，为 false 则该列即使全列同值/全空也不折叠；默认 true                                                                    |

- **选项类型**：`RecordBoardOptionItem` 为 `{ label, value, color? }`，只读态 Tag 可使用 `color`。`RecordBoardGetOptions`
  为同步或异步返回选项列表的函数类型。
- **列显示隐藏**：`columnVisibility`、`onColumnVisibilityChange` 由容器内部维护并注入 Context；ToolBar 通过
  `toolBar.columnSetting`（默认 true）控制是否展示列设置入口，Grid/CardList 按可见列渲染。不通过 props 传入。

### 列 type 与能力对照

| 能力   | 有 type                    | 无 type                |
|------|---------------------------|-----------------------|
| 表格展示 | 自动只读组件；可传 render 覆盖       | 纯文本或自定义 render        |
| 筛选   | 自动编辑态；可传 filter 覆盖        | 需 filterable + filter |
| 排序   | 自动 SortOptions；可传 sort 覆盖 | 需 sortable + sort     |

## 相关文档

- [Shell Frame 外壳说明](./ShellFrame.md)（工具栏/批量栏/分页模块拆解）
- [设计文档](./PisellRecordBoard.$tab-design.md)（概念、数据流、扩展）
- [开发文档](./PisellRecordBoard.$tab-dev.md)（目录结构、实现细节、与 PisellFloorMapLayout 分支）
- [测试文档](./PisellRecordBoard.$tab-test.md)（单测建议、Story、回归清单）
- 平面图能力详见 **`pisellFloorMapLayout`** 组件文档（同包 `docs/`）
