> **本文档集顺序**:① [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) |