---
description: "一屏一父：详情页里同时把「父记录概览」和它的一对多子表摊开来看。相比 master-detail（列表+侧栏）和 record-tabs（子表塞进 tab），关联记录是把子表直接嵌在父页上——同页可以并排两三份"
---

# 关联记录（父记录 + 内联子列表）

一屏一父：详情页里同时把「父记录概览」和它的**一对多子表**摊开来看。相比 master-detail（列表+侧栏）和 record-tabs（子表塞进 tab），关联记录是**把子表直接嵌在父页上**——同页可以并排两三份。

<iframe src="/skills/dashboard/assets/demo-related-records.html"
        style="width:100%;height:660px;border:1px solid #8884;border-radius:10px"
        loading="lazy" title="形状 demo：关联记录"></iframe>

试这几下：

- 顶部一张**客户信息卡**（父记录），下面一张**关联订单**表（子记录，5 行起）
- 标题右侧的**计数徽标** = 表里行数，添加/移除后立即刷新
- 点**添加关联** —— 弹层里从候选池（未关联的订单）搜索并勾选，加入后 toast
- 每行右侧**移除** —— 破坏性操作，先弹确认再执行；确认后 toast
- 全部关联完，候选池清空，「添加关联」按钮自动禁用

## 规矩

- **子表只做展示**：组件对行类型泛型，把「已按父 id 过滤好的行」传进去，它自己不发请求。demo 里 rows 由 `orderIds` 派生；真实版是子资源自己的 `list query({ filters: { <parentId>: id } })` 拉出来传入
- **关系落在关联表，不是自定义 join**：添加/移除是一次 mutation，成功后**失效父资源 + 该子资源 list 键**，两边同步刷新
- **计数徽标 = rows.length**，永远跟着 rows 走，别单独维护一个计数状态
- **移除先确认**，添加/移除后都出 toast；空表有干净的空态；候选池空了 `添加关联` 禁用
- 一页可以挂多份 `RelatedList`（比如客户下同时挂订单和发票），每份独立列定义、独立空态、独立 View all 链接

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

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

    ```html
    --8<-- "skills/dashboard/assets/demo-related-records.html"
    ```

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