feat: 实现导航页基础功能
- 添加导航条目和分类数据模型 - 实现导航卡片网格展示 - 支持点击卡片打开链接 - 添加主题样式(渐变色、卡片阴影) - 区分系统默认和自定义条目 - 添加分类统计信息"
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
# 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` 这类没有对应元素的导航,滚动偏移不变。
|
||||
- 点「提交网站」不会弹出任何东西。
|
||||
Reference in New Issue
Block a user