Files
Navius/docs/index3-alignment.md
CNWei 1c98340fc5 feat: 实现导航页基础功能
- 添加导航条目和分类数据模型
- 实现导航卡片网格展示
- 支持点击卡片打开链接
- 添加主题样式(渐变色、卡片阴影)
- 区分系统默认和自定义条目
- 添加分类统计信息"
2026-09-19 21:53:15 +08:00

174 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# index3.html 逐条对照
本文档把 `index3.html` 里的每一条 CSS 规则与 JS 行为,逐条对应到 Flutter 实现的位置,用来回答「有没有跑偏」。
对照方式:`index3.html` 里的选择器 / 声明 → Flutter 文件与常量。
## 1. 设计变量
| index3.html | Flutter |
| --- | --- |
| `--bg-color: #f7f9fb` | `AppColors.background` |
| `--card-bg: #ffffff` | `AppColors.surface` |
| `--primary-color: #3b82f6` | `AppColors.primary` |
| `--text-main: #1e293b` | `AppColors.textPrimary` |
| `--text-sub: #64748b` | `AppColors.textSecondary` |
| `--border-color: #e2e8f0` | `AppColors.border` |
| `--shadow` | `AppColors.cardShadow` |
| `--shadow-hover` | `AppColors.cardShadowHover` |
| `--sidebar-width: 230px` | `AppSizes.sidebarWidth` |
| `--sidebar-collapsed-width: 64px` | `AppSizes.sidebarCollapsedWidth` |
## 2. 全局
| index3.html | Flutter |
| --- | --- |
| `* { box-sizing: border-box; margin: 0; padding: 0 }` | Flutter 无 box-sizing 概念,等价于默认行为 |
| `html { scroll-behavior: smooth }` | `Scrollable.ensureVisible(..., duration: 300ms)` |
| `body { background-color: var(--bg-color) }` | `Scaffold.backgroundColor = AppColors.background` |
| `body { color: var(--text-main) }` | `AppTheme.light` + 各文字样式自带颜色 |
| `body { font-family: -apple-system, ... }` | 不指定 `fontFamily`,沿用系统字体栈 |
| `body { display: flex; min-height: 100vh }` | `Scaffold.body = Stack[ Row[侧栏, Expanded(内容)] ]` |
## 3. 侧栏
| index3.html | Flutter |
| --- | --- |
| `.sidebar { width: 230px }` | `SidebarNav` 根 `AnimatedContainer.width` |
| `.sidebar { background-color: card-bg }` | `AppColors.surface` |
| `.sidebar { border-right: 1px solid border }` | `BoxDecoration(border: Border(right: ...))` |
| `.sidebar { padding: 20px 12px 12px 12px }` | `sidebarPaddingTop / Horizontal / Bottom` |
| `.sidebar { flex-direction: column }` | `Column` |
| `.sidebar { height: 100vh }` | `Row(crossAxisAlignment: stretch)` |
| `.sidebar { transition: width .3s cubic-bezier(.4,0,.2,1) }` | `sidebarTransition` + `Curves.fastOutSlowIn`(该曲线即 Material standard easing) |
| `.sidebar { overflow-x: hidden }` | 根部 `ClipRect` |
| `body.sidebar-collapsed .sidebar { width: 64px; padding: 20px 8px 12px 8px }` | `sidebarCollapsedWidth` + `sidebarCollapsedPaddingHorizontal` |
| `.sidebar-header { height: 40px; margin-bottom: 24px; 居中 }` | `_SidebarHeader`(`sidebarHeaderHeight` / `sidebarHeaderGap`) |
| `.logo { font-size: 18px; font-weight: 700; color: primary }` | `AppTextStyles.logo` |
| `.logo-icon { font-size: 20px; font-weight: 800; color: primary }` | `AppTextStyles.logoIcon` |
| `body.sidebar-collapsed .logo-text { display: none }` | 折叠时渲染 `logoIcon` 分支 |
| `body.sidebar-collapsed .logo-icon { display: block }` | 同上 |
| `.nav-container { flex: 1; overflow-y: auto; overflow-x: hidden }` | `Expanded(child: SingleChildScrollView(...))` |
## 4. 导航
| index3.html | Flutter |
| --- | --- |
| `.nav-item-wrapper { margin-bottom: 4px }` | `_NavEntry` 的 `Padding(bottom: navItemGap)` |
| `.nav-link { padding: 11px 16px; border-radius: 8px }` | `navItemPaddingVertical/Horizontal`、`navItemRadius` |
| `.nav-link { font-size: 14px; font-weight: 500 }` | `AppTextStyles.navItem` |
| `.nav-link { color: text-sub }` | `AppColors.textSecondary` |
| `.nav-link { justify-content: space-between }` | `Row(mainAxisAlignment: spaceBetween)` |
| `.nav-link { cursor: pointer }` | `MouseRegion(cursor: SystemMouseCursors.click)` |
| `.nav-link { transition: all .2s }` | `AnimatedContainer(duration: 200ms, curve: Curves.ease)` |
| `.nav-link:hover { background-color: #f8fafc; color: primary }` | `AppColors.navItemHover` + `_hovered` |
| `.nav-link.active { background-color: #eff6ff; color: primary }` | `AppColors.primaryFaint` + `selected` |
| `.arrow-icon { font-size: 14px; font-weight: bold; color: #94a3b8; font-family: monospace }` | `AppTextStyles.navArrow` |
| `.arrow-icon { transition: transform .3s ease }` | `AnimatedRotation(duration: 300ms, curve: Curves.ease)` |
| `.nav-item-wrapper.open > .nav-link .arrow-icon { transform: rotate(90deg) }` | `turns: open ? 0.25 : 0` |
| `.sub-menu { padding-left: 16px; max-height: 0; overflow: hidden }` | `subMenuIndent` + `AnimatedSize`(未展开时不渲染子项) |
| `.sub-menu { transition: max-height .3s cubic-bezier(0,1,0,1) }` | `curve: const Cubic(0, 1, 0, 1)` |
| `.open .sub-menu { max-height: 500px; transition: max-height .3s ease-in-out }` | `curve: Curves.easeInOut` |
| `.sub-menu .nav-link { padding: 8px 16px; font-size: 13px }` | `_SubNavItem` + `AppTextStyles.subNavItem` |
| `body.sidebar-collapsed .nav-text { display: none }` | 折叠时 `_NavRow` 不渲染任何子节点 |
| `body.sidebar-collapsed .arrow-icon { display: none }` | 折叠时 `_NavRow` 走无 trailing 分支 |
| `body.sidebar-collapsed .sub-menu { display: none }` | 折叠时根本不构建 `AnimatedSize` |
| `body.sidebar-collapsed .nav-link { justify-content: center; padding: 12px 0 }` | `navItemCollapsedPaddingVertical` + 水平 0 |
## 5. 侧栏底部
| index3.html | Flutter |
| --- | --- |
| `.sidebar-footer { border-top: 1px solid border; padding-top: 12px; margin-top: 8px }` | `_SidebarFooter` |
| `.submit-btn { padding: 10px 16px; border-radius: 8px; font-size: 14px; font-weight: 500 }` | `submitButtonPaddingVertical/Horizontal`、`AppTextStyles.submitButton` |
| `.submit-btn { color: text-sub; transition: all .2s }` | `AnimatedContainer(curve: Curves.ease)` |
| `.submit-btn:hover { background-color: #f1f5f9; color: primary }` | `AppColors.navItemHoverDeep` |
| `.submit-btn` 的 `title="提交网站"` | `Tooltip('提交网站')` |
| `body.sidebar-collapsed .submit-text { display: none }` | 折叠时不渲染文字 |
## 6. 主内容区与顶栏
| index3.html | Flutter |
| --- | --- |
| `.main-content { margin-left: 230px / 64px }` | `Row[ SizedBox(width: 侧栏), Expanded(内容) ]` |
| `.main-content { transition: margin-left .3s cubic-bezier(.4,0,.2,1) }` | 侧栏宽度动画,同一曲线 |
| `.main-content { padding: 24px 40px 40px 40px }` | `contentPaddingTop / Horizontal / Bottom` |
| `.top-bar { gap: 16px; max-width: 750px; margin: 0 auto 36px auto }` | `TopBar`(`topBarGap` / `topBarMaxWidth` / `topBarMarginBottom` + `Center`) |
| `.toggle-btn { width/height: 44px; border-radius: 12px; border: 1px solid border }` | `toggleButtonSize` / `toggleButtonRadius` |
| `.toggle-btn { box-shadow: var(--shadow) }` | `AppColors.cardShadow` |
| `.toggle-btn { font-size: 18px; color: text-sub }` | `Text('☰', fontSize: 18)` |
| `.toggle-btn { transition: all .2s }` | `AnimatedContainer(curve: Curves.ease)` |
| `.toggle-btn:hover { border-color: primary; color: primary }` | `_hovered` 分支 |
| `.toggle-btn` 的 `title="展开/折叠侧边栏"` | `Tooltip('展开/折叠侧边栏')` |
| `.search-container { flex: 1 }` | `Expanded(child: LinkSearchField)` |
| `.search-box { height: 44px; padding: 0 20px; border-radius: 12px; font-size: 15px }` | `searchHeight` / `searchPaddingHorizontal` / `searchRadius` / `AppTextStyles.searchInput` |
| `.search-box { box-shadow: var(--shadow) }` | 未聚焦时的 `boxShadow` |
| `.search-box:focus { border-color: primary; box-shadow: 0 0 0 3px rgba(59,130,246,.15) }` | 聚焦时**只用**光晕(覆盖默认阴影,与 CSS 一致) |
| `.search-box { transition: border-color .2s, box-shadow .2s }` | `AnimatedContainer(curve: Curves.ease)` |
## 7. 分组与卡片
| index3.html | Flutter |
| --- | --- |
| `.section-title { font-size: 16px; font-weight: 600; margin-bottom: 16px }` | `AppTextStyles.sectionTitle` + `sectionTitleGap` |
| `.section-title::before { width: 4px; height: 16px; margin-right: 8px; border-radius: 2px }` | `_SectionTitle` 的竖条(`sectionAccent*`) |
| `.grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px }` | `AppBreakpoints.gridColumnsFor` + `gridGap` |
| `.grid { margin-bottom: 36px }` | 每个分区统一 `sectionGap` 下间距(最后一个也一样) |
| `.card { border-radius: 10px; padding: 14px 16px; border: 1px solid border }` | `cardRadius` / `cardPadding*` / `AppColors.border` |
| `.card { box-shadow: var(--shadow); transition: transform .2s, box-shadow .2s }` | `AnimatedContainer(curve: Curves.ease)` |
| `.card:hover { transform: translateY(-2px); box-shadow: var(--shadow-hover) }` | `cardHoverLift` + `cardShadowHover` |
| `.card-icon { width/height: 36px; border-radius: 8px; background: #f1f5f9; color: primary }` | `_CardIcon`(`cardIconSize` / `cardIconRadius` / `AppColors.iconSurface`) |
| `.card-icon { font-weight: 600; margin-right: 12px }` | `AppTextStyles.cardIcon` + `cardIconGap` |
| `.card-icon` 未设 font-size,继承 body 的 16px | `AppTextStyles.cardIcon.fontSize = 16` |
| `.card-info { overflow: hidden }` + `.card-title/.card-desc` 单行省略 | `Expanded` + `maxLines: 1, overflow: ellipsis` |
| `.card-title { font-size: 14px; font-weight: 600 }` | `AppTextStyles.cardTitle` |
| `.card-desc { font-size: 12px; color: text-sub; margin-top: 2px }` | `AppTextStyles.cardDesc` + `cardTitleGap` |
## 8. 回到顶部
| index3.html | Flutter |
| --- | --- |
| `.back-to-top { position: fixed; bottom: 30px; right: 30px }` | `Positioned(right: 30, bottom: 30)`(`backToTopOffset`) |
| `.back-to-top { width/height: 44px; border-radius: 50% }` | `backToTopSize` + `BoxShape.circle` |
| `.back-to-top { font-size: 18px; color: text-sub; box-shadow: var(--shadow) }` | `Text('↑', fontSize: 18)` + `cardShadow` |
| `.back-to-top { transition: all .2s ease }` | `AnimatedContainer(curve: Curves.ease)` |
| `.back-to-top:hover { color/border-color: primary; transform: translateY(-3px); box-shadow: hover }` | `backToTopHoverLift` + `cardShadowHover` |
| `.back-to-top` 的 `title="回到顶部"` | `Tooltip('回到顶部')` |
## 9. JS 行为
| index3.html 的脚本 | Flutter |
| --- | --- |
| 点 `#toggleBtn` → `body.classList.toggle('sidebar-collapsed')` | `HomePage._toggleSidebar` |
| 点 `.has-sub` → 若已折叠先移除 `sidebar-collapsed` | `HomePage._toggleGroup` 里 `collapsed` 为真时先 `_collapsedOverride = false` |
| 点 `.has-sub` → `parentItem.classList.toggle('open')` | 同一批 id 集合 `_openGroupIds` 的增删 |
## 10. 当前实现没有任何原型之外的行为
以下都是「原型里没有」的行为,**都不实现**:
| 原型的状态 | 当前实现 |
| --- | --- |
| 搜索框没有任何 JS | 只接收输入,不过滤内容;没有空状态、没有清空按钮 |
| `active` 写死在「常用推荐」上 | 高亮固定不动,点其它导航项也不会变 |
| 没有媒体查询 | 侧栏始终 230px(点折叠按钮变 64px),主内容内边距始终 `24/40/40/40` |
| `#icon`、`#stock`、`#dev`、`#api` 没有对应元素 | 这四个导航项点击后没有任何反应(不滚动、不报错) |
| `#submit` 没有对应元素 | 点「提交网站」没有任何反应 |
| 只有 `#common`(3 张卡)与 `#design`(2 张卡)两段内容 | 内容区就是这两段、五张卡片 |
历史上曾经额外加过、现已全部删除的:滚动的侧栏联动、折叠态首字短标签、分组包含当前分区时的强调色、点二级条目自动展开分组、卡片上弹出网址的 tooltip、搜索过滤与空状态、点导航切换高亮、窄屏自动折叠、补齐缺失的四个分区、提交按钮的提示。
## 11. 验证方式
- `flutter analyze` → No issues found
- `flutter test` → 36 个用例通过
- `flutter build web --no-wasm-dry-run` → 构建成功
其中几条专门守住「不加戏」的边界:
- 滚动主内容时,侧栏第一行仍是**同一个 widget 实例**(没有被重建),高亮不变。
- 折叠态下 `navText('常用推荐')`、`navText('提交网站')` 都找不到——文字确实被隐藏了,与 `display: none` 一致。
- 输入搜索词后,仍然是 2 个分区、5 张卡片。
- 点 `#icon` 这类没有对应元素的导航,滚动偏移不变。
- 点「提交网站」不会弹出任何东西。