---
description: "表格的姊妹形状：同一资源换一种呈现——响应式卡片画廊。搜索/筛选/排序/分页的参数管道与表格完全同一份，只有渲染从行换成卡片。封面、摘要这类\"一眼看内容\"胜过\"逐列扫数据\"的资源，用它"
---

# 卡片列表（同一列表管道的卡片画廊）

表格的姊妹形状：同一资源换一种呈现——响应式卡片画廊。搜索/筛选/排序/分页的参数管道与表格**完全同一份**，只有渲染从行换成卡片。封面、摘要这类"一眼看内容"胜过"逐列扫数据"的资源，用它。

<iframe src="/skills/dashboard/assets/demo-card-list.html"
        style="width:100%;height:640px;border:1px solid #8884;border-radius:10px"
        loading="lazy" title="形状 demo：卡片列表"></iframe>

试这几下：

- 搜索、状态筛选、排序下拉、分页组合使用——全都作用于同一份列表参数；空结果有空态卡片
- 拉窄窗口——卡片列数自适应（CSS grid `auto-fill`），不用媒体查询
- 点卡片上的**上架/下架**——状态 chip 立即翻转 + toast；在「在售」筛选下点下架，那件商品当场离开列表
- 切到**表格**视图再切回来——搜索词、筛选、排序、页码原样保留：两种呈现共用同一条参数管道

## 规矩

- **与表格同一份 `ListParams`**：`{ page, pageSize, search?, sortBy?, sortDir?, filters? }` → `{ rows, total }`，查询键、URL 状态也一致——卡片列表只是换了呈现。demo 在内存里用一个 `listProducts()` 跑完过滤排序分页；真实版把同一份参数交给同一个 `Repository.list()`（服务端执行）
- **卡片页和表格页共用 `useResourceList`**：一个钩子包掉列表状态 + 查询，页面只决定渲染 `CardList` 还是 `DataTable`。demo 的卡片/表格切换演的就是这条——视图换了，列表状态一动不动；真实版这个钩子还把状态同步进 URL
- **卡片渲染是资源自己的事**：`renderCard` + 稳定的 `getKey`，每张卡等价于表格的一行——标题、状态 chip、若干 meta、操作。真实版放在 `cards.tsx`（平行于 `columns.tsx`），操作走 `ActionMenu` 的 `context` 回调
- 增删改沿用表格页那一套：创建/编辑弹窗 + `useConfirm` 删除，成功后 toast + 失效查询键——demo 的上/下架直接改内存数组，真实版换成 mutation

> 蓝本：`add-card-list.md`[^add-card-list]（open-dashboard @ `aa9815f`，MIT，Invariants 已消化进上面「规矩」）

??? abstract "demo 源码：`assets/demo-card-list.html`（自包含、未压缩）"

    ```html
    --8<-- "skills/dashboard/assets/demo-card-list.html"
    ```

[^add-card-list]: [`add-card-list.md`](https://github.com/ahpxex/open-dashboard/blob/aa9815f426e9520841c4aa15a341bfd9f866091f/.claude/skills/add-component/references/add-card-list.md)，2026-07-28 读。
