From 87308be0e679cc8c6b17aefcd8751e0b7a58188e Mon Sep 17 00:00:00 2001
From: Tevin <tingquanren@163.com>
Date: Mon, 13 Apr 2026 11:06:30 +0800
Subject: [PATCH] refactor(side-menu): 移除旧的 ADR 文档并新增产品说明文档

---
 /dev/null                                     |   89 --------
 openspec/docs/old-refactors/side-menu/task.md |   79 ++++---
 openspec/docs/old-refactors/side-menu/spec.md |  250 +++++++++++--------------
 openspec/docs/old-refactors/side-menu/prod.md |  142 ++++++++++++++
 4 files changed, 292 insertions(+), 268 deletions(-)

diff --git a/openspec/docs/old-refactors/side-menu/adr.md b/openspec/docs/old-refactors/side-menu/adr.md
deleted file mode 100644
index 82cd6b5..0000000
--- a/openspec/docs/old-refactors/side-menu/adr.md
+++ /dev/null
@@ -1,89 +0,0 @@
-# 侧栏菜单(Side Menu)— 架构 / 产品决策
-
-**状态**:生效中
-
-本文记录侧栏菜单相关**取舍**;行为细节以 `spec.md` 为准,**可勾选验收**以 `task.md` 为准。
-
----
-
-### 同级手风琴式展开
-
-- **状态**:生效
-- **上下文**:多级菜单若允许多个同级分支同时展开,信息密度高、滚动长,且与常见后台导航习惯不一致。
-- **决策**:在**同一父节点下**,同时仅保留一个展开子分支;用户显式收起时尊重其操作。
-- **后果**:需在 Ant Design `Menu` 的 `onOpenChange` 与自定义子菜单层**两处**保持一致语义,避免二级与三级行为分裂。SPEC §4.2。
-
----
-
-### 断点以下采用固定全宽 + 遮罩
-
-- **状态**:生效
-- **上下文**:窄屏下窄侧栏可读性与可点性差,且需让出主内容区。
-- **决策**:低于 `lg` 量级断点时侧栏改为覆盖式(fixed),展开时配合全宽类表现与遮罩,点击遮罩关闭。
-- **后果**:与宿主折叠状态强耦合;须通过回调同步。SPEC §3.2、§4.5。
-
----
-
-### 固定模式下延迟导航
-
-- **状态**:生效
-- **上下文**:收起动画与立即路由/开页同时进行时易出现闪烁或竞态。
-- **决策**:固定模式下先触发收起,再延迟约 300ms 调用「打开页」。
-- **后果**:实现须严格保证顺序;延迟与壳子动画时长应对齐。SPEC §4.3。
-
----
-
-### 自定义滚动条而非纯系统滚动条
-
-- **状态**:生效
-- **上下文**:深色主题侧栏与系统滚动条样式、宽度不一致时破坏视觉对齐;需统一轨道与滑块。
-- **决策**:在内容可滚动时使用自绘轨道/滑块,并与 `scrollTop` 同步;resize 时重算。
-- **后果**:实现复杂度高于原生滚动条;需处理拖拽与遮罩的交互冲突及多段动效。SPEC §4.4、§4.5、§5.3。
-
----
-
-### 菜单数据由宿主注入
-
-- **状态**:生效
-- **上下文**:权限、租户、动态路由等差异应在壳子或数据层处理。
-- **决策**:组件只消费树与运行态,不内置请求与策略。
-- **后果**:SPEC 必须写清数据字段与回调语义,否则对接易歧义。SPEC §2、§6。
-
----
-
-### 静态路径骨架与接口权限数据分离
-
-- **状态**:生效
-- **上下文**:常见做法是静态配置中的 `treePaths` 固定页面路径与分组,接口返回分组/条目做权限与展示控制,二者在宿主或数据层合并。
-- **决策**:侧栏组件不直接读取静态配置文件;仅展示宿主传入的**已合并** `tree`。路径与 `pageName` 的权威定义在数据层。
-- **后果**:「菜单从哪来」须在宿主或数据层单独说明;侧栏 TASK 以合并后数据验收。SPEC §2.3、§2.4。
-
----
-
-### 侧栏外「按 pageName 找页」与多级树的潜在不一致(宿主债)
-
-- **状态**:生效(已知限制)
-- **上下文**:若宿主提供「按 `pageName` 打开页」的辅助逻辑,且实现为仅在每个分组下**一层** `items` 上线性扫描,则一旦静态或合并后的树出现**嵌套二级以上的可点击页**,会找不到或行为错误。
-- **决策**:不在侧栏组件内解决;由宿主保证「按名开页」与真实树深度一致(必要时改为递归查找)。
-- **后果**:侧栏实现三级 UI 时,应提醒宿主团队校对同类辅助方法。与 SPEC §2.1 层级支持配套。
-
----
-
-### `onSetMenuCollapse` 支持 boolean 与无参两种调用
-
-- **状态**:生效
-- **上下文**:`Layout.Sider` 断点回调常传入 `broken`,顶栏按钮则多触发无参「切换」。
-- **决策**:宿主统一处理:`boolean` 时设为对应折叠态,无参时按约定切换或收起。
-- **后果**:侧栏须能触发上述两类调用,避免 antd@6 升级后只处理其中一种。SPEC §6.2。
-
----
-
-## 修订记录
-
-| 日期 | 摘要 |
-|------|------|
-| 2026-04-07 | 初稿 |
-| 2026-04-07 | 明确验收以 task.md 为准 |
-| 2026-04-07 | 补充路径骨架与 API 分离、折叠回调双形态、按 pageName 查找层级限制 |
-| 2026-04-07 | 去除具体文件/类名,改为职责级描述 |
-| 2026-04-07 | 节号随 SPEC §5 动效 / §6 契约调整 |
diff --git a/openspec/docs/old-refactors/side-menu/prod.md b/openspec/docs/old-refactors/side-menu/prod.md
new file mode 100644
index 0000000..c2a4978
--- /dev/null
+++ b/openspec/docs/old-refactors/side-menu/prod.md
@@ -0,0 +1,142 @@
+> **本文档集顺序**:① [prod.md](./prod.md)(产品与决策)→ ② [spec.md](./spec.md)(规格)→ ③ [task.md](./task.md)(验收)。编写与修订亦建议按此顺序。
+
+# 侧栏导航(Side Menu)— 产品说明
+
+## 1. 业务职能与要解决的问题
+
+本组件是**管理后台壳层**的**左侧主导航**:在固定宽度侧栏内展示按业务模块分组的菜单树,并与「已打开的子页签」状态联动,使用户在多模块、多层级入口之间快速跳转;在视口变窄时仍以折叠 / 全宽覆盖等方式保持可用。
+
+要解决的核心问题:
+
+- **树状模块结构**(含多级入口):保持当前位置可辨识(选中、父级高亮、已打开页签提示)。
+- **菜单可能超高**:仍能完整浏览(纵向滚动;在满足宽屏媒体条件时用与侧栏风格一致的自绘滚动条,避免原生条割裂视觉)。
+- **大屏与窄屏**:展开/收拢侧栏与点击打开子页的时序一致,避免动画未完成就切页。
+
+**非目标**:子页内容、路由注册、权限与数据请求;本组件只消费宿主传入的树与状态。
+
+主要使用者:登录后使用后台的运营/管理员等。
+
+信息结构(便于与 spec 对齐):
+
+- **一级**:业务分组,由 antd `Menu.SubMenu` 承载(分组标题即模块名)。
+- **分组以内**:节点若仍有子列表,则由**自研子菜单层**递归展开(解决部分环境下嵌套 `SubMenu` 展示异常的问题);无子列表则为叶子,点击打开页。
+
+---
+
+## 2. 用户感知(人如何理解这个组件)
+
+### 可见状态
+
+- **整体**:深色竖向导航;顶区为站点/产品标题;下方为可滚动菜单树。
+- **一级分组**:分组标题;若子树中有当前页,分组须有「子树选中」可辨样式。
+- **分组内仍含下级**:由自研子菜单层呈现,可展开/收起,箭头随展开在「V 形」与「展开」之间变化;选中落在本节点与选中落在子级时标题强调程度不同(本节点选中时更接近「当前页」色块)。
+- **叶子项**:类型图标与文案;若对应窗格已在「已打开」列表中,须有与未打开可区分的样式(如右侧小箭头仅在「已打开」时明显出现)。
+- **窄屏**:侧栏可变为盖住主内容的固定层;展开时可占满可视宽度;可配半透明遮罩,点遮罩收起(拖拽自绘滑块时遮罩语义见 spec §5)。
+- **菜单超高**:在宽屏媒体下,右侧出现细轨 + 滑块(非系统默认宽条外观);拖拽滑块时整壳层级与遮罩反馈与平时不同。
+
+### 断点与「固定 / 覆盖」(补充详述)
+
+- **大屏**:主导航是左侧固定宽度竖条,主内容在右,菜单不「浮」在整页上。
+- **屏变窄**:侧栏进入固定覆盖语义;仅当用户展开时才以全宽抽屉压在主内容之上,收起后主内容再占满。
+
+### 自定义滚动条(宽屏,与 `lg` 断点同量级 992px)(补充详述)
+
+- 一屏装不下时,在满足宽屏媒体且判定需要滚动后,右侧出现与深色侧栏一致的细条,而非突兀的系统条。
+- 指针移入可滚菜单区时滑块先略提亮,再移到滑块热区则变宽、变亮(两级 hover 可感知差异)。
+- 拖拽时列表跟手,滑块颜色立刻到位(关闭颜色过渡以免迟滞);若同时存在遮罩层,则近透明且不应误触关闭逻辑。
+- 不溢出时右侧无可点死区(轨不接收指针)。
+
+### 表现行为
+
+- 分组与自研子菜单的展开/收起有过渡(收起与展开时长可略有不同,约 0.2s~0.3s 量级);侧栏与内容区过渡与现网一致(约 0.3s 量级)。
+- 自绘轨在需要时渐入(可带 delay);拖拽时关闭颜色过渡以保证跟手。
+
+### 交互反馈
+
+- **点叶子**:大屏立即打开页;窄屏固定覆盖模式下先收侧栏,短延迟后再打开页。
+- **点遮罩**(非拖拽滚动条、且遮罩为可见半透明时):收起侧栏。
+- **拖拽自绘滑块**:内容与滑块联动;拖拽中遮罩与层级语义见 [spec.md](./spec.md) §5。
+- **resize**:重算是否需滚动条与滑块比例。
+
+### 交互反馈阶梯(自绘滚动条,白话)
+
+从「离滚动条较远」到「正在拖拽」,用户应能感到至少三档:① 菜单区可滚但未对准轨/柄;② 对准轨或滑块热区,轨与滑块明显可交互;③ 按下拖拽,高对比、过渡关闭。详见 spec §5 表格化谓词,便于验收对齐。
+
+---
+
+## 3. 操作时序与流程
+
+### 主路径(打开某叶子功能)
+
+1. 在树中找到目标叶子并点击。
+2. **大屏**(非固定覆盖):宿主立即收到「打开页」。
+3. **窄屏固定覆盖态**:先收起侧栏 → 约 300ms 后宿主收到「打开页」。
+
+### 分支
+
+- 手风琴:同一父级下新开一侧同级则收起其他同级展开(antd 一级 `SubMenu` 与自研子菜单层两条更新路径写入同一套 `openKeys` 语义须一致)。
+- 只收起不导航:点遮罩等 → 不收新页。
+- resize:可能在「要/不要自绘滚动条」「是否进入窄屏固定覆盖」间切换。
+
+### 顺序约束
+
+- 窄屏下须先收侧栏再打开页(延迟与动画对齐)。
+- 展开导致高度变化后,须在动画稍后再检测是否需滚动(避免算错高度);一级菜单与自研层回调后的延迟可略有不同,但均须完成一次可靠检测。
+
+### 断点与覆盖(典型时序)
+
+1. 由宽变窄 → 宿主与内部态随 `breakpoint` 同步,常呈现收起、主区全宽。
+2. 用户展开菜单 → 固定覆盖 + 可配遮罩;点遮罩收起。
+3. 再拉宽 → 恢复左侧固定条与主内容并排。
+
+### 自定义滚动条(典型时序)
+
+1. 列表超高 → 判定需滚动后,轨与滑块延时淡入。
+2. 移入菜单滚动区 → 滑块先中间强调色。
+3. 移到滑块热区 → 加宽、高亮。
+4. 拖拽 → 联动滚动;遮罩按 spec §5;滑块关闭颜色 transition。
+5. 松开 → 恢复 hover 与过渡。
+6. 不再溢出 → 轨隐藏且不挡指针,内边距恢复。
+
+---
+
+## 4. 产品决策与架构(逻辑与决策一体)
+
+### 为何需要自研子菜单层(分组内「仍有子列表」)
+
+- **上下文**:antd `Menu.SubMenu` 嵌套在部分移动端/窄视口下曾有多级展示异常。
+- **决策**:分组内凡仍有 `items` 的节点用自研子菜单层(外层列表项 + 内嵌 `Menu`);一级分组仍用 antd `SubMenu`。
+- **后果**:`openKeys` 同时服务两套展开来源;迁移 antd v6 须回归多级与触摸。
+
+### 手风琴式同级展开
+
+- **上下文**:纵向空间有限。
+- **决策**:新展开一项时,从展开集合中移除同级其他 key(基于树结构求同级)。
+- **后果**:非 antd 默认的「多同级全开记忆」;迁移勿擅自改成全开除非产品要求。
+
+### 自绘纵向滚动条(宽屏媒体下且内容溢出)
+
+- **上下文**:原生条与深色侧栏不协调,且需与箭头、内边距对齐。
+- **决策**:在宽屏媒体且内容超出可视区(含容差阈值)时自绘轨与滑块,transform 移滑块并与 `scrollTop` 比例同步;不满足媒体条件时不依赖自绘轨(由布局内滚动或抽屉承担)。
+- **后果**:resize 与展开后须重算;拖拽与遮罩、z-index 协同见 spec §5。
+
+### 遮罩
+
+- **上下文**:窄屏覆盖需提示「壳外」并可点关;拖拽滚动条时不应误关。
+- **决策**:窄屏展开时半透明遮罩;拖拽滚动条时遮罩可仍占位但近透明,侧栏容器抬升 z-index 以免挡拖拽。
+
+### 宿主侧「按页名找页」与深层树(已知边界)
+
+若宿主存在仅在分组下浅层扫描的辅助逻辑,而真实菜单深于两层,可能找不到叶子——须由宿主与数据层保证一致;详见 [adr.md](./adr.md) 相应条。
+
+---
+
+## 5. 对 spec 与 task 的指向
+
+| 内容 | 落点 |
+|------|------|
+| 树字段、展开/选中/点击顺序 | [spec.md](./spec.md) §2–§4 |
+| 根容器语义、断点、`Sider`、布尔组合 | spec §3 |
+| 遮罩、自绘滚动条、子菜单动效 | spec §5 |
+| 可测条目 | [task.md](./task.md) |
+| 历史 ADR 式条目(可选对照) | [adr.md](./adr.md) |
diff --git a/openspec/docs/old-refactors/side-menu/spec.md b/openspec/docs/old-refactors/side-menu/spec.md
index 28aa707..8c0014f 100644
--- a/openspec/docs/old-refactors/side-menu/spec.md
+++ b/openspec/docs/old-refactors/side-menu/spec.md
@@ -1,207 +1,177 @@
-# 侧栏菜单(Side Menu)— 规格说明
+> **本文档集顺序**:① [prod.md](./prod.md) → ② **本页 spec** → ③ [task.md](./task.md)。请先读 prod;验收见 task。
+
+# 侧栏导航 — 规格说明
 
 ## 1. 概述与范围
 
-### 1.1 业务职能
+本规格描述壳层侧栏的**技术契约**:数据树、受控展开、响应式侧栏、antd `Layout.Sider` / `Menu` 的组合,以及分组内多级由**自研子菜单层**承载(现网实现可拆为独立组件,迁移时可替换实现,语义须一致)。业务目标与用户叙事以 [prod.md](./prod.md) 为准。
 
-- 本组件承担**整站(管理后台)的主导航**:在典型布局中**常驻于视口左侧**,是用户从任意业务页跳转到其它功能模块的**一级入口**(与顶栏、中部多标签内容区共同构成「壳」;侧栏负责「去哪」,内容区负责「看什么」)。
-- 菜单项与**可打开的业务页**一一对应(具体路由、嵌入页、权限裁剪由宿主与数据层决定,见 §2、§6)。
-- 窄屏下仍承担同一职能,仅**呈现形态**改为覆盖式抽屉(§3.2),不改变「主导航」定位。
+- **依赖**:React、antd v6 目标栈下的 `Layout.Sider`、`Menu`(dark、inline)。
+- **范围**:侧栏容器、菜单渲染、遮罩与自绘纵向滚动条;不含路由与子页实现。
+- **非目标**:不复述 antd 通用 API 手册;只写与默认不一致或叠加的约定。
 
-### 1.2 职责(能力概要)
-
-在壳布局中提供**可折叠的纵向导航**:展示多级菜单树,标识当前选中项与已打开页签,在窄屏下以「全宽抽屉式」呈现并支持遮罩关闭。
-
-### 1.3 非目标
-
-- 不在本组件内实现路由注册、权限过滤、菜单数据请求(由宿主提供树数据)。
-- 不重复阐述 **antd@6** 已提供的通用能力(`Layout.Sider`、`Menu` 内联/深色主题等以官方文档为准)。
-
-### 1.4 技术栈
-
-实现目标:**React**,**antd@6**(自 v4.3 升级场景);SPEC 仅描述与壳子及自定义逻辑相关的约定。
+**写法**:以行为、谓词与可验收参数为主,不锁定现网内部 class/state/函数名;实现可重命名,须保持等价语义。下列 antd/React 对外概念(如 `openKeys`、`onBreakpoint`)保留。
 
 ---
 
 ## 2. 数据模型
 
-### 2.1 菜单树(由宿主传入)
+### 菜单树(宿主传入)
 
-- 顶层为**分组**列表(一级),每项包含子列表 `items`(二级及以下)。
-- 每个节点至少具备:
-  - **标识**:`id`(优先)或 `key`,在整棵树中用于选中、展开状态;比较时按字符串语义相等即可。
-  - **展示名**:`name`。
-  - **可选类型**:`type`,用于图标映射;常见取值见 §2.3;未匹配时使用默认图标。
-- **子节点**:若存在非空 `items`,则该节点为**可展开**的父节点;否则为**叶子**(可点击打开页)。
-- **层级**:须支持**至少三级**(一级分组 → 二级可展开节点 → 叶子),以合并逻辑与侧栏子菜单的递归结构为准;§2.3 所述静态配置多为「分组 → 叶子」二级,运行时以合并后的 `tree` 为准。
+- 顶层为数组;每项为一级分组,由 antd `Menu.SubMenu` 承载,含:
+  - 标识:`id`(优先)或 `key`,全树用于选中与展开(与 `openKeys`、`selectedKeys` 字符串化一致)。
+  - 展示名:`name`(作分组标题)。
+  - 子列表:`items`;其内为二级及以下节点。
+- 分组内节点(任意深度):
+  - 若仍有非空 `items` → 可展开父节点,由自研子菜单层渲染,可递归;
+  - 否则为叶子(点击打开页)。
 
-### 2.2 宿主提供的运行态
+### 运行态
 
-- **当前选中页键**:与某叶子或节点的 `id`/`key` 对应,用于菜单选中高亮及「子树选中」样式。
-- **已打开页签列表**:用于在叶子上显示「已打开」样式(例如某 `pane.key` 与节点 `id` 一致则标记)。
+- 当前选中键:与某节点标识一致,驱动 `selectedKeys`(与窗格 key 字符串一致)。
+- 已打开窗格列表:用于叶子「已打开」样式;判定为某窗格 key 与叶子 `id` 一致(若项目统一用 `id`/`key` 之一,须全树一致)。
+- collapsed / title:宿主驱动折叠;顶区文案。
 
-### 2.3 静态路径配置(数据结构约定)
-
-以下描述**静态路径骨架**的常见 JSON 形态,便于对齐字段语义(存放文件名与路径由项目自定)。
-
-- 根对象可含 `projectName`(工程标识)、`treePaths`(分组数组)、`hiddenPaths`(**不在侧栏展示**的页面映射,供其它入口按 `pageName` 打开;侧栏可不渲染此项,但应知晓 `id` 可能为负数等特殊键,以免选中/标签逻辑异常)。
-- **`treePaths` 每一项(一级分组)**:`id`、`name`、`items`。
-- **`items` 内叶子(典型)**:`id`、`name`、`pageName`、`path`;可选 `type`(如 `chart`、`setting` 等)。若某节点再含非空嵌套 `items`,则为中间层,须按 §4.2 三级规则处理。
-- 合并后叶子可带业务/权限相关字段(由接口与宿主侧合并逻辑写入,如 `forbid`、`show` 在合并阶段已过滤);**侧栏只消费合并后的 `tree`**,不负责请求。
-
-### 2.4 合并与权限(宿主职责,侧栏不实现)
-
-- 典型流程:接口返回的分组/条目与静态骨架在**宿主或数据层**合并,并按 `forbid`、`show` 等规则剔除不可见项(具体函数名与模块划分由项目自定)。
-- 合并后 `type` 缺省时常规范为 `'normal'`(与图标默认分支一致)。
-- 若沿用「静态配置 + 接口权限」形态,须在**宿主层**复现或等价实现合并;侧栏 SPEC 仍以「输入树已最终可用」为前提。
-
-### 2.5 点击叶子时传递给宿主的载荷
-
-- 「打开页」回调应传入**完整菜单项对象**(至少包含宿主开页所需字段,如 `id`、`name`、`path`、`pageName`),以便宿主增加标签、拼接构建版本等查询参数等与既有开页逻辑一致。
+可选:静态配置与接口合并、`hiddenPaths`、负数 id 等由宿主与数据层约定,侧栏以输入树已最终可用为前提。
 
 ---
 
 ## 3. 布局与响应式
 
-### 3.1 常规(宽屏)
+### 根容器与 `Layout.Sider`(须可观测)
 
-- 侧栏为固定**内容区宽度**的纵向区域(目标约 210px,含与滚动条占位相关的补偿时可由实现决定,但须避免内容被系统滚动条挤压错位)。
-- 侧栏可处于**展开**或**收起**状态;收起宽度为 0(不占可视内容区),由宿主控制 `collapsed`。
+外层根容器高度随壳层占满;下列语义由内部 state、宿主 `collapsed`、`Layout.Sider` 的 breakpoint 与拖拽态共同决定(实现可用任意 class,须满足谓词):
 
-### 3.2 断点与「固定」模式
+| 语义 | 谓词(逻辑合取) |
+|------|------------------|
+| **窄屏固定覆盖态** | `Sider` 的 breakpoint 已打破(现网为 `lg`,与 ≥992px 媒体自绘轨大致同量级);内部记录与 `onBreakpoint(true)` 一致 |
+| **窄屏且抽屉展开为全宽** | 窄屏固定覆盖态且宿主 `collapsed === false` |
+| **窄屏且收起不占全宽** | 窄屏固定覆盖态且 `collapsed === true`(侧栏收至零宽量级,主内容区占满) |
+| **自绘滚动条拖拽中** | 用户正在拖拽自绘滑块(与 §5 联动);根容器进入「滚动拖拽」语义,z-index 抬升 |
+| **宽屏侧栏并排** | 非窄屏固定覆盖态;侧栏为常规并排,不施加「全宽抽屉压在主内容上」 |
 
-- 当视口宽度低于约定断点(与 `lg` 量级一致)时,进入**固定(fixed)**布局模式:
-  - 侧栏行为接近**覆盖在内容之上的抽屉**:展开时占满可视宽度(或产品约定的全宽表现),并与宿主协作设置 `collapsed`。
-  - 宿主在断点变化时应被通知以同步折叠状态(例如 `onSetMenuCollapse(broken)` 语义)。
+断点联动(顺序与契约):
 
-### 3.3 顶区标题
+1. `onBreakpoint(broken)` 触发时,须调用 `onSetMenuCollapse(broken)`,并使内部「窄屏固定覆盖」与 `broken` 同步。
+2. 不得假设仅断点一侧变化:宿主 `collapsed` 与断点共同决定「全宽展开」是否出现。
 
-- 侧栏顶部展示站点/产品标题(由宿主传入);区域宽度与下方菜单内容区对齐(含滚动条占位策略一致)。
+#### 用户能感知到什么(断点)
+
+- 大屏:左侧固定宽度条,主内容在右。
+- 屏变窄:进入固定覆盖语义;不展开时主内容占满。
+- 主动展开才呈现全宽压在主内容之上;收起后再占满。
+
+#### 典型操作时序(断点)
+
+1. 由宽变窄 → 宿主与内部态随断点同步,常先收起,主区全宽。
+2. 用户展开菜单 → 全宽抽屉 + 遮罩;点遮罩收起。
+3. 再拉宽 → 恢复并排布局。
+
+#### 实现要点(与根容器对照)
+
+- `onBreakpoint` → `onSetMenuCollapse(broken)` 与内部窄屏态同一布尔。
+- 全宽抽屉仅当窄屏且未 collapsed。
+- 拖拽滚动条时根容器抬升 z-index,遮罩近透明但仍可参与指针路由(见 §5),避免挡滑块。
+
+### 顶区与可滚内容区
+
+- 顶区固定高度量级约 50px;宽度与下方菜单区一致,并随滚动条占位宽度加宽(与菜单可滚区同步测量)。
+- 菜单区内容宽度约 210px 量级 + 占位;可视高度 + 容差(现网约 10px)< 内容高度时判定「需要纵向滚动」,驱动宽屏自绘轨显示(§5)。
+- window resize 时重测滚动需求与滑块比例。
 
 ---
 
 ## 4. 交互与状态(逻辑)
 
-### 4.1 一级分组
+### `openKeys`(受控)
 
-- 一级分组由 antd `SubMenu` 承载常规展示;若当前选中项落在该分组子树内,该分组须有**可区分的选中子树**样式(例如高亮父级)。
+- 两套来源写入同一状态:① antd 一级 `Menu` 的 `onOpenChange`(仅作用于一级分组 `SubMenu`);② 自研子菜单层的展开/收起回调(现网用独立回调名,避免与 `Menu` 内部拦截冲突)。
+- 同级手风琴:当检测到新展开的 key 时,在树中求同级其他 key,从本次意图集合中剔除同级其他再合并;纯收起则按传入集合更新。
+- 展开后滚动检测:须在动画/布局稳定后再次检测是否需纵向滚动;一级路径与自研路径延迟可不同(现网约 0.35s~0.5s 量级),须各自完成一次 `_checkScrolling` 等价逻辑,避免滑块高度为 0 或永久不可滚。
 
-### 4.2 展开态(openKeys,受控扩展)
+### 选中
 
-- **内层 Menu**:在 `onOpenChange` 中实现**同级手风琴**——用户**新展开**某节点时,**同一父级下**已展开的**兄弟**节点须关闭;用户主动收起时以传入的 `openKeys` 为准。(非 antd 默认,须自实现。)
-- **自定义子菜单层**(三级等):展开某一 key 时从 `openKeys` 移除其**兄弟** key 再并入当前 key;关闭时仅移除该 key。展开/关闭后须在 DOM/动画稳定后**重新检测**是否需滚动条(允许短延迟,如 ~300–500ms)。
+- `selectedKeys` 与当前选中键字符串形式一致。
+- 一级分组:子孙中有选中 → 子树选中样式(现网为分组标题区可辨强调)。
+- 自研子菜单层:选中在本节点与选中在子级两种标题样式可区分(见 §5)。
 
-### 4.3 点击叶子
+### 点击叶子
 
-- **宽屏**:立即调用宿主的「打开页」回调。
-- **固定模式**:先通知宿主**收起**侧栏,再在**短延迟**(约 300ms,与侧栏动画匹配)后调用「打开页」回调,避免动画与导航冲突。
+- 非窄屏固定覆盖:立即 `onClickMenuItem(item)`(载荷为完整 item)。
+- 窄屏固定覆盖态:先 `onSetMenuCollapse()`(或等价收起),约 300ms 后 `onClickMenuItem(item)`。
 
-### 4.4 滚动与自定义滚动条(逻辑)
+### 自绘滚动条(逻辑)
 
-- 当菜单内容高度超过可视区域超过约 10px 时,视为**需要纵向滚动**,并切换「需滚动」状态(供 §5 样式与 §4.5 遮罩联动)。
-- 提供**自定义滚动条轨道与滑块**(非仅依赖系统滚动条):滑块高度与可视比例成正比;拖拽滑块与菜单 `scrollTop` **双向同步**;支持鼠标拖拽与触摸拖拽;拖拽期间由 React 状态标记**滚动拖拽中**(与 §5 动效、§4.5 遮罩一致)。
-- 窗口 **resize** 时须更新滚动条占位宽度及滚动需求检测。
-
-### 4.5 遮罩(逻辑)
-
-- 在**固定模式且侧栏展开**时显示遮罩;点击遮罩应通知宿主收起(若在滚动拖拽中则不应因遮罩点击关闭)。
-- 自定义滚动条拖拽过程中遮罩与 §5.3「滚动拖拽」、§5.2 遮罩层级一致;点击关闭语义以 §4.5 与 §5 联合为准。
+- 滑块高度与可视/内容高度成比例;滑块位移与 `scrollTop` 双向同步;支持鼠标与触摸(按下/移动/结束)。
+- 内容滚动驱动滑块时仅更新位移,不重复写 `scrollTop`(避免抖动);拖拽时按位移比例写回 `scrollTop`。
 
 ---
 
 ## 5. 自定义样式与动效
 
-本节描述**自研叠加层**(非 antd 默认 token 可完整表达)的视觉与动效契约;实现可用任意 class 命名,但须复现状态、时长与层级关系。
+用户感知与操作时序全貌见 [prod.md](./prod.md) §2–§3。本节给状态、时长、层级与参数目标,实现可用任意样式方案,须可验收。
 
-### 5.1 顶区标题条(Logo 区)
+### 遮罩
 
-- 固定高度(目标约 50px)、全宽、**主色实底**、白字加粗;过长文案 **ellipsis**。
-- 与下方滚动区纵向衔接,滚动区高度为「剩余视高」(如 `calc(100% - 顶区高度)`)。
+| 项目 | 约定 |
+|------|------|
+| 可见(半透明) | 窄屏且侧栏展开且非拖拽滚动条的典型态;背景透明度目标约 0.2(黑底) |
+| 近透明 | 自绘滚动条拖拽中:背景约完全透明,仍占据全屏命中区时需配合 z-index(见下)避免误点关闭 |
+| 点击 | 非拖拽滚动条时点击遮罩 → `onSetMenuCollapse()`(或等价);拖拽中点击不收拢 |
 
-### 5.2 固定模式与遮罩(非滚动拖拽)
+层级(目标关系):窄屏下固定覆盖容器整体 z-index 高于主内容;拖拽中侧栏区域须高于近透明遮罩,以免滑块被挡。现网量级:容器拖拽态约 50、遮罩约 10、侧栏内容区约 2(实现可调整,须保持相对关系)。
 
-- **固定定位**:侧栏容器覆盖视口;**全宽展开**时使用独立 class 将宽度拉满。
-- **遮罩层**:默认隐藏;在固定模式且侧栏展开时显示,**半透明深色**(如 rgba 黑 ~0.2),铺满视口,**低于侧栏内容**的 z-index,点击触发收起(逻辑见 §4.5)。
+### 自绘滚动条(宽屏媒体 + 内容溢出)
 
-### 5.3 自定义滚动条(宽屏,约 ≥992px)
+出现条件(合取):视口 ≥ 约 992px(与 `Sider` 的 `lg` 断点同量级)且判定内容超高(§3 容差规则)且进入「显示轨」状态。
 
-**出现与布局**
+#### 交互反馈阶梯(指针 / 命中域)
 
-- **不需要滚动**时:自绘轨道**不可见**(如 `opacity: 0`)且 **`pointer-events: none`**,避免挡操作。
-- **需要滚动**时:菜单内容区增加**右侧内边距**(目标约 20px)为轨道留位;轨道贴侧栏**右缘**固定列宽(目标约 20px),**深底**与侧栏深色主题协调;轨道整体**淡入**可用 `transition`,并带 **delay**(与菜单展开/重排动画衔接,目标约 **0.3s 延迟 + 0.2s 过渡**量级)。
-- 实际滚动仍由内容区 `overflow-y: scroll` 承担;自绘滑块仅**视觉与拖拽**同步 `scrollTop`。
+| 阶梯 | 命中谓词(语义) | 视觉与过渡目标 |
+|------|------------------|----------------|
+| **外围上下文** | 宽屏且溢出,未 hover 可滚菜单区;轨可 opacity 0→1 渐入(轨整体可带 delay,如约 0.3s delay + 0.2s 过渡量级) | 轨上滑块「细、深」,低对比 |
+| **控件邻近带** | 指针进入可滚菜单区(`.c-menu-scroll-show` 等价语义),尚未指向滑块主热区 | 滑块条先变为中间强调色(如灰蓝),宽度可仍为窄条 |
+| **主操作面** | 指针落在滑块热区(轨内滑块可拖区域) | 滑块加宽(如约 6px→12px)、更亮;background / width 过渡约 0.3s |
+| **激活 / 拖拽中** | mousedown / touch 拖拽滑块 | 保持加宽与高亮;background-color 等 `transition: 0s`;遮罩按上表近透明 |
 
-**滑块(thumb)**
+不需要滚动时:轨不可见且 `pointer-events: none`(或等价),不形成右侧死区;菜单区无为轨预留的额外右内边距。
 
-- 默认较**细**(目标宽约 6px)、居中偏右、**圆角**条、色值深于轨道;`background` / `width` / `margin` 变化带过渡(目标约 **0.3s**),hover 时**加宽、变色**(目标宽约 12px、更高对比浅色)。
-- **滚动区域 hover**(非必须点在滑块上):thumb 可先进入**中间色**态,再与滑块自身 hover 的**最亮**态区分层次。
+需要滚动时:内容区保留纵向滚动(`overflow-y: scroll` 语义),右侧内边距(目标约 20px)为轨留位;轨宽约 20px,贴于可滚区右侧;滑块在轨内用 transform: translateY 与 `scrollTop` 同步。
 
-**拖拽中(与 §4.4 `scrollDraging` 等状态对应)**
+与 JS 的边界:「是否溢出」「是否宽屏」由检测与媒体查询共同决定;拖拽中由 React state 切换根容器「拖拽」语义并联动遮罩样式。
 
-- 容器进入「滚动拖拽」class 时:thumb 保持**展开宽度与高亮主色**,且对 **background(及必要时相关属性)关闭 transition(0s)**,避免跟手时出现颜色/过渡迟滞。
-- 同时 **遮罩**若仍处于显示态:变为**全透明**,仍占位或可点,**提高 z-index**,使拖拽过程中交互意图与固定模式下的实心遮罩区分(与 §4.5 逻辑一致:不阻挡拖拽结束后的常规点击语义由实现统一)。
+### 一级菜单与叶子(深色主题局部)
 
-### 5.4 菜单深色主题上的局部覆盖(节选)
+- hover / selected 与叶子右侧 Caret 箭头:未打开时常隐藏或极弱,已打开叶子须可见可辨。
+- 一级分组子树选中:分组标题区与默认态可区分。
 
-- 一级 `SubMenu` 标题在 active/open/子树选中等态下**背景提亮**(rgba 白低不透明度阶梯)。
-- 叶子项:`hover` / `selected` 背景与右侧 **Caret** 箭头显隐、颜色变化带**短过渡**;**已打开**叶子的箭头常显,未打开则隐藏,逻辑见数据模型,样式与 §4 选中态一致。
+### 自研子菜单层
 
-### 5.5 可访问性与降级
+- 标题区箭头由两段线模拟,展开时旋转可感知。
+- 子列表 max-height:收起向 0、展开向大值过渡;收起与展开 duration 可不同(现网约 0.2s 收起 / 0.3s 展开),缓动可用 antd 常用 cubic-bezier。
+- 选中在本节点:标题背景接近「当前页」强调色;仅有子级选中:标题为弱强调(颜色/字重与上一档可辨)。
 
-- 若产品无要求,可暂不实现 `prefers-reduced-motion`;迁移时建议评估:至少保证**键盘可达性**不与自定义滚动条 `pointer-events` 冲突。
-- 窄于 §5.3 断点时以系统滚动或全宽抽屉为主,自绘轨道可隐藏(与 §3.2 一致)。
+### 可访问性与降级
+
+- `prefers-reduced-motion`:产品未强制时可注明「当前不约束」;若实现,应同步缩短或关闭展开与轨渐入。
+- 键盘焦点与自绘轨:当前不强制与原生滚动条等价,但须避免「轨不挡指针」与焦点环逻辑冲突。
 
 ---
 
 ## 6. 与宿主应用的契约
 
-### 6.1 输入(概念)
-
-| 概念 | 说明 |
-|------|------|
-| 菜单树 | 见 §2.1~§2.4 |
-| 标题 | 顶区展示文案(常与接口站点名同源;与静态配置里的 `projectName` 可能不同源) |
-| 是否收起 | 宿主控制折叠 |
-| 当前选中键 | 与节点 `id`/`key` 对齐;常见为**数值型菜单 id**(含 `hiddenPaths` 场景的负数 id) |
-| 已打开页列表 | 用于叶子「已打开」样式;项上 `key` 与菜单项 `id` 一致 |
-
-### 6.2 输出(回调)
-
-| 回调语义 | 时机 |
-|----------|------|
-| 设置折叠 | 断点变化、用户点遮罩、叶子点击前(固定模式)等;参数可为 **boolean**(是否与断点「broken」对齐)或**无参**(切换/收起);宿主须兼容:**boolean 时设为对应折叠态,无参时按约定的切换语义处理**。 |
-| 打开菜单项 | 用户点击叶子;载荷为 **§2.5** 菜单项对象;固定模式下在收起动画后再触发 |
-
-### 6.3 壳布局集成(建议 props 映射,命名可改)
-
-侧栏通常与主区域**兄弟**排列:一侧为侧栏,另一侧为 `Layout`(顶栏、多标签内容区等)。折叠状态应由宿主**单一数据源**驱动,并与顶栏等共享同一套折叠回调。
-
-建议输入/输出与下列概念对齐(prop 名可重命名,语义须一致):
-
-| 侧栏侧(示例名) | 含义 |
-|------------------|------|
-| `title` | 顶区标题 |
-| `tree` | 合并后的分组树 |
-| `panesOnShelf` | 已打开页签列表(用于「已打开」样式) |
-| `collapsed` | 是否收起 |
-| `curActivePaneKey` | 当前选中页键 |
-| `onClickMenuItem` | 打开/激活标签页 |
-| `onSetMenuCollapse` | 折叠:支持 boolean 与无参(见 §6.2) |
+| 概念 / 常见 prop 名 | 说明 |
+|---------------------|------|
+| 菜单树 | §2 |
+| 顶区标题 | `title` |
+| `collapsed` | 宿主驱动;与 `Sider` collapsed 绑定 |
+| 当前选中键 | `curActivePaneKey` → `selectedKeys` |
+| 已打开列表 | `panesOnShelf`;叶子「已打开」判定见 §2 |
+| `onSetMenuCollapse` | 断点传入 boolean;遮罩、窄屏点叶子前等可为无参收起;语义见 §3–§4 |
+| `onClickMenuItem` | 叶子;载荷为完整 item;窄屏时序见 §4 |
 
 ---
 
 ## 7. 验收
 
 验收标准见 [task.md](./task.md)。
-
----
-
-## 8. 修订记录
-
-| 日期 | 摘要 |
-|------|------|
-| 2026-04-07 | 初稿,抽象侧栏菜单行为契约 |
-| 2026-04-07 | 目标栈明确为 React + antd@6;验收迁至 task.md |
-| 2026-04-07 | 对齐静态路径配置、壳集成与宿主合并/回调语义;去除具体仓库路径 |
-| 2026-04-07 | 新增 §5 自定义样式与动效(滚动条/遮罩/Logo);宿主契约顺延为 §6 |
-| 2026-04-07 | 新增 §1.1 业务职能(整站主导航与壳布局中的角色) |
diff --git a/openspec/docs/old-refactors/side-menu/task.md b/openspec/docs/old-refactors/side-menu/task.md
index 002ed7d..c9e5e96 100644
--- a/openspec/docs/old-refactors/side-menu/task.md
+++ b/openspec/docs/old-refactors/side-menu/task.md
@@ -1,55 +1,56 @@
-# 侧栏菜单(Side Menu)— 验收标准
+> **本文档集顺序**:① [prod.md](./prod.md) → ② [spec.md](./spec.md) → ③ **本页 task**。依据与条目应对齐 prod 与 spec。
 
-验收项应对照 [spec.md](./spec.md);技术实现基于 **React + antd@6**,下列不重复验证 antd 默认组件能力(如 Menu 基础受控、`Sider` 常规折叠属性等),仅验证**本文档约定与叠加逻辑**。
+# 侧栏导航 — 验收标准
 
-**样本数据**:可使用与 SPEC §2.3、§2.4 一致的静态骨架 + 合并规则构造最小样例树;文档不绑定某仓库内具体文件路径。
+在 **antd@6** 目标栈下可执行(手工或 E2E)。参数与语义以 [spec.md](./spec.md) §5 为准;不要求与现网 class 名一致。
 
----
+## 数据与结构
 
-## 数据与展示
+- [ ] 顶层 `tree` 非法组合不渲染错误结构;有 `items` 时一级分组标题与 `name` 一致。
+- [ ] 叶子标识与菜单 `key`/`selectedKeys` 一致;`curActivePaneKey` 命中叶子时该叶为 **selected**。
+- [ ] 分组下任意深度有选中时,该一级分组具 **子树选中** 可辨样式。
 
-- [ ] 空树不报错,侧栏可显示标题与空菜单区域。
-- [ ] 使用 SPEC 约定字段(`id`、`name`、`pageName`、`path`、可选 `type`)时,分组名与叶子文案展示正确。
-- [ ] `type` 为 `chart` / `setting` 等与 spec 约定映射一致;缺省或 `normal` 时走默认图标分支。
-- [ ] `id` 与 `key` 混用时,选中与展开比较均正确(字符串语义一致);**数值型** `curActivePaneKey`(含负数 id 场景)与 Menu `selectedKeys` 类型转换正确。
-- [ ] 当前选中在深层叶子时,对应一级分组呈现「子树选中」样式(见 spec §4.1)。
-- [ ] 已打开页签对应的叶子显示「已打开」样式(`pane.key ===` 菜单项 `id`,见 spec §2.2)。
+## 响应式与壳层(根容器组合)
 
-## 展开行为(超出 antd 默认的同级互斥)
+- [ ] 跨越 `Sider` **breakpoint**(如 lg)时宿主收到 `onSetMenuCollapse(broken)`,且界面「**窄屏固定覆盖**」与 broken 一致。
+- [ ] **窄屏**且 `collapsed === false` 时呈现 **全宽抽屉** 语义;窄屏且 `collapsed === true` 时不呈现全宽展开。
+- [ ] 窄屏且展开时遮罩为 **可见半透明**(约 0.2 透明度量级,可肉眼对照 spec §5);点遮罩收起。
+- [ ] 由窄回宽后无永久遮罩挡主内容操作。
 
-- [ ] 三级结构下,在 antd `Menu` 层展开二级某分支时,其**同级**其它已展开分支被关闭(spec §4.2 手风琴)。
-- [ ] 三级自定义子菜单层:展开某一 key 时**兄弟** key 从 `openKeys` 移除;关闭时仅移除当前 key(spec §4.2)。
-- [ ] 展开/关闭动画结束后,滚动区域「是否需要滚动」的判定会更新(允许短延迟)。
+## 展开与手风琴
 
-## 滚动与遮罩(逻辑)
+- [ ] 同父级下连续展开两个一级 `SubMenu`,最终仅保留新展开项(同级其他被收起)。
+- [ ] **自研子菜单**路径:展开一节点时同级另一节点从 `openKeys` 中移除。
+- [ ] 展开/收起后,在约 0.35s~0.5s 内完成高度与滚动检测;内容足够高时无「永久无法滚动」或「滑块高度为 0」。
 
-- [ ] 内容超出阈值(约 10px)时出现自定义滚动条;滑块与 `scrollTop` 双向同步;触摸拖拽可用(spec §4.4)。
-- [ ] `resize` 后滚动条占位与是否需要滚动判断正确。
-- [ ] 固定模式且侧栏展开时显示遮罩;点遮罩触发收起;**滚动拖拽过程中**不因误点遮罩关闭(spec §4.5)。
+## 点击与宿主回调
 
-## 自定义样式与动效(须对照 spec §5)
+- [ ] **非窄屏固定覆盖**:点叶子立即 `onClickMenuItem(item)`。
+- [ ] **窄屏固定覆盖**:先触发收起,约 300ms 后 `onClickMenuItem(item)`。
+- [ ] **点遮罩**(非拖拽滚动条、且为可见半透明遮罩):触发收拢。
 
-- [ ] **无需滚动**时自绘轨道不可见且 `pointer-events` 不阻挡菜单操作;**需要滚动**时轨道在约定 **delay + duration** 后出现(spec §5.3)。
-- [ ] 出现滚动条时菜单区有约定 **padding-right**,一级 `SubMenu` 与自定义子菜单的箭头位置与 spec §5.3 一致(不与轨道重叠)。
-- [ ] 滑块默认细、hover **加宽与变色**有过渡(约 0.3s);**滚动拖拽**态下滑块保持展开态且 **background 等关闭 transition(0s)**(spec §5.3)。
-- [ ] **滚动拖拽**时遮罩呈全透明、容器 **z-index** 与 §5.3 一致,与固定模式下半透明遮罩可区分。
-- [ ] 顶区标题条高度、主色底、ellipsis 符合 spec §5.1。
-- [ ] 固定模式全宽 class 与遮罩 rgba 层级符合 spec §5.2。
+## 自绘滚动条 — 出现与同步(spec §5)
 
-## 宿主契约与窄屏
+- [ ] 仅当视口 ≥ 约 992px 且内容超高(含容差)时出现轨与滑块;否则轨不可见且不形成右侧可点死区。
+- [ ] 拖拽时 `scrollTop` 与滑块位移比例一致;松手后滚动与滑块仍同步。
+- [ ] **拖拽中**:遮罩 **近透明**;点遮罩/遮罩区域不收拢侧栏(与 spec §5 一致)。
+- [ ] **非拖拽**且窄屏展开:**半透明遮罩**可点收拢。
 
-- [ ] 低于约定断点时进入固定模式;断点变化时宿主收到折叠同步(spec §3.2)。
-- [ ] `onSetMenuCollapse`:传入 **boolean** 时宿主折叠态与之一致;**无参**调用时符合 spec §6.2 约定的切换语义。
-- [ ] 固定模式下点击叶子:先触发收起,再于约定延迟后调用「打开页」回调,顺序符合 spec §4.3。
-- [ ] 点击叶子时,回调参数为**完整菜单项对象**,至少包含宿主开页所需字段(如 `id`、`name`、`path`、`pageName`),与宿主标签/开页逻辑的消费方式一致(spec §2.5)。
+## 自绘滚动条 — 交互反馈阶梯(spec §5 表格)
 
----
+- [ ] 外围 → 邻近:先进入可滚菜单区,滑块条较默认态明显提亮(中间色),宽度可仍为窄条。
+- [ ] 邻近 → 主操作面:指针落在滑块热区,滑块加宽(如约 6px→12px)且更亮,过渡约 0.3s 量级。
+- [ ] 拖拽(激活):滑块保持加宽高亮;`background-color` 等 `transition` 为 0,无跟手迟滞。
 
-## 修订记录
+## 自研子菜单
 
-| 日期 | 摘要 |
-|------|------|
-| 2026-04-07 | 自 spec §6 迁入;标注 antd@6 与基线不验范围 |
-| 2026-04-07 | 对齐静态配置字段与壳回调验收 |
-| 2026-04-07 | 去除具体文件路径 |
-| 2026-04-07 | 宿主契约节号改为 §6.2;新增 §5 动效验收 |
+- [ ] 展开时箭头旋转与 max-height 过渡可感知;收起与展开时长可不同,无闪断。
+- [ ] 选中在本节点与仅有子级选中时标题样式可区分。
+
+## 已打开窗格
+
+- [ ] `panesOnShelf` 中存在 `pane.key` 与叶子 `id` 一致的项时,该叶具 **已打开** 可辨样式,**Caret** 规则符合 spec §5。
+
+## 回归(v6)
+
+- [ ] 窄视口/触摸下分组内多级仍可展开并点选叶子。

--
Gitblit v1.9.1