# 布局外壳

`ConsoleLayout` 负责所有外壳行为：侧栏、顶栏、页签栏、面包屑、内容区、偏好与无障碍设置。
页面只渲染业务内容，不重复实现布局。

## 三种布局模式

在「设置 → 外观 → 布局」里切换，偏好存在浏览器（`appearance` 存储键下）：

| 模式            | 值         | 表现                                                           |
| --------------- | ---------- | -------------------------------------------------------------- |
| 侧边导航        | `side`     | 左侧固定侧栏 + 顶栏；侧栏可折叠成图标条，宽度可调              |
| 顶部 + 侧边导航 | `top-side` | 顶部主导航 + 左侧分组侧栏，适合菜单层级多的系统                |
| 顶部导航        | `top`      | 只有顶部主导航，侧边菜单由顶部点击展开（覆盖层），不占内容宽度 |

顶部导航模式下：侧栏里的官网入口与品牌字标不出现（避免和顶部重复），顶部只显示头像图标，
面包屑与顶部菜单分两行，展开的侧边菜单跟随顶部菜单滚动。

## 顶栏快捷操作

默认顺序固定为：

| 顺序 | 操作             | id              |
| ---- | ---------------- | --------------- |
| 1    | 搜索（命令面板） | `search`        |
| 2    | **通知**         | `notifications` |
| 3    | 刷新页面         | `reload`        |
| 4    | 全屏             | `fullscreen`    |
| 5    | 语言             | `language`      |
| 6    | 主题             | `theme`         |
| 7    | 仓库（自绘图标） | `github`        |

通知默认排第二，是产品约定：通知是最高频的全局入口。顺序可以在「设置 → 外观 → 顶栏快捷操作」里调整，
新版本新增的入口会按默认位置插回，不会因为用户改过顺序就消失（`resolveHeaderActions`）。
业务系统也可以直接覆盖某一个 id 的节点：

```tsx
<ConsoleLayout
  headerActions={{
    reload: <PageReloadButton />,
    notifications: <NotificationsButton />,
  }}
/>
```

## 命令面板

`⌘K`（Windows：`Ctrl+K`）呼出命令面板，搜索范围只有两处：**菜单**，以及**当前页已渲染的内容**。

- 索引方式：遍历主内容区的**文本节点**（`src/lib/content-search.ts`），用文本所在元素做定位目标；
  徽标、状态这种 `<span>` 文案也能搜到，不再依赖固定标签。
- 上限 800 条 / 单条 240 字符，按路由缓存；页面内容变化时用 `MutationObserver` 标记失效、下次搜索重建。
- 只索引**当前路由**已渲染的内容：虚拟滚动的行、未展开的折叠区、异步还没回来的数据不在索引里。
- 键盘 `↑`/`↓` 选择、`Enter` 打开、`Esc` 关闭。
- 主键用 `⌘K`：命令面板的通行做法，也和文档站（VitePress）的搜索键一致；`⌘⇧S` 保留为兼容别名（`⌘S` 被浏览器"保存网页"占用）。

## 快捷键

默认组合是 `g` 开头的两段式跳转：

| 组合  | 目标     |
| ----- | -------- |
| `g d` | 工作台   |
| `g c` | 客户管理 |
| `g n` | 通知中心 |
| `g s` | 设置     |

在「设置 → 快捷键」里可以改组合；输入冲突组合会直接提示并拒绝保存（`shortcutConflict`）。

## 页签栏与页面过渡

- 页签栏样式：`card`（卡片）与 `line`（下划线）两种，见「设置 → 外观 → 页签」。
- 页面过渡支持 `none`、`fade`、`slide`、`slide-left`、`slide-right`、`slide-up`、`slide-down` 与 `auto`。
- `auto` 按导航方向自动选：前进向左滑入、后退向右滑入、首屏与 replace 淡入。
- 点顶栏"刷新"时不播进入动画（`dataset.transition = 'none'`），避免整屏闪一下。
- 打开「减少动效」后所有过渡自动降级为无动画。

## 侧栏与内容区

- 侧栏宽度可拖拽调整（键盘 `←`/`→` 也可），折叠态固定 76px 图标条。
- 菜单支持层级：父节点展开后缩进显示子项，当前路由所在分支自动展开；外链菜单带外链图标，按 `target` 决定新窗口 / 当前窗口 / iframe 内嵌（见[动态菜单与权限](/guide/dynamic-menu)）。
- 收藏夹按用户 ID 隔离（`favoritesKey`），最多保存常用页面，缺权限的项会标记失效而不是静默消失。
- 侧栏底部默认显示外链（`footerLinks`，可多项、每项可指定图标），传 `userControl` 可换成用户菜单。
- 「最大化页面」会临时隐藏页签栏与面包屑，适合看大表格。

## ConsoleLayout 常用 props

| prop                  | 类型                                         | 说明                                          |
| --------------------- | -------------------------------------------- | --------------------------------------------- |
| `navigation`          | `NavigationItem[]`                           | 全部菜单项，平铺传入                          |
| `groups`              | `NavigationGroupDefinition[]`                | 分组定义；不传则所有菜单落在一组              |
| `title`               | `string`                                     | 浏览器标题后缀，同时用于移动端侧栏描述        |
| `brand` / `brandHref` | `ReactNode` / `string`                       | 品牌节点与点击目标，默认用 `public/brand`     |
| `footerLinks`         | `{ href, label, icon? }[]`                   | 侧栏底部外链（官网、仓库等，可多项）          |
| `favoritesKey`        | `string`                                     | 传入即启用收藏夹，作为隔离键（通常用用户 ID） |
| `accountControl`      | `ReactNode`                                  | 品牌下方的账户 / 租户切换控件                 |
| `userControl`         | `ReactNode`                                  | 侧栏底部用户菜单，不传时显示 `footerLinks`    |
| `headerActions`       | `Partial<Record<HeaderActionId, ReactNode>>` | 覆盖顶栏快捷操作节点                          |
| `pageTitles`          | `Record<string, string>`                     | 动态路由的面包屑标题，取最长匹配父路径        |
| `fallbackTitle`       | `string`                                     | 既不在菜单也不在 `pageTitles` 时的标题兜底    |
