---
description: "列一多表格就挤。这个形状在工具栏加一个「列」popover：复选框逐列切换显隐，身份列锁死不可藏，外加舒适/紧凑两档密度；偏好持久化，下次进来还是你的布局。它只管「怎么看」，不碰「看什么」——排序、筛选照常工作"
---

# 列控制（列显隐 + 密度）

列一多表格就挤。这个形状在工具栏加一个「列」popover：复选框逐列切换显隐，身份列锁死不可藏，外加舒适/紧凑两档密度；偏好持久化，下次进来还是你的布局。它只管「怎么看」，不碰「看什么」——排序、筛选照常工作。

<iframe src="/skills/dashboard/assets/demo-table-columns.html"
        style="width:100%;height:600px;border:1px solid #8884;border-radius:10px"
        loading="lazy" title="形状 demo：列控制"></iframe>

试这几下：

- 点工具栏的**列**——逐列勾选显隐，表格立刻重排；「城市」默认就是藏着的，勾上它试试
- popover 里找不到**姓名**——身份列 `enableHiding: false`，不给用户藏掉「这一行是谁」
- 切密度到**紧凑**——行高与内边距肉眼可见地变小；**重置**恢复默认列显隐
- 藏几列、换个密度，然后刷新页面（iframe 内右键 → 重新加载框架）——偏好还在（localStorage）
- 先按**工单数**排序、再搜个名字，然后随便藏列——排序与筛选不受任何影响

## 规矩

- **显隐与密度都是受控状态，不留内部副本**：显隐是一张 `列 id → bool` 的映射，密度归父组件所有；popover 只是读写这两个状态的开关面板。demo 用一个 React state + localStorage，真实版是 TanStack Table 的 `columnVisibility`（`state` + `onColumnVisibilityChange`）接用户偏好持久化
- **身份列退出隐藏**：`enableHiding: false` 的列直接不进 popover（对应 `column.getCanHide()` 过滤）——用户可以少看，但不能把行的身份藏掉
- **开关的名字读 `meta.label`**：每个列定义带一个人类可读标签，与表头渲染解耦——表头可以是任意组件，复选框的名字始终干净
- **列控制与查询状态各管各的**：显隐/密度是「怎么看」（视图偏好），排序/筛选/分页是「看什么」（列表参数）——两套状态互不引用，藏掉排序列也不影响排序结果

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

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

    ```html
    --8<-- "skills/dashboard/assets/demo-table-columns.html"
    ```

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