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 文档并新增产品说明文档 --- openspec/docs/old-refactors/side-menu/spec.md | 250 ++++++++++++++++++++++---------------------------- 1 files changed, 110 insertions(+), 140 deletions(-) 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 业务职能(整站主导航与壳布局中的角色) | -- Gitblit v1.9.1