# Polaris 首页实现规格 > 日期:2026-08-26 > > 状态:用户已批准,可进入实施 > > 实现分支:`feat/polaris-homepage` > > 历史状态(2026-10-14 补记):本规格是首页阶段的实施依据,正文(含范围、视觉与验收要求)保留 2026-08-26 时点的决策原貌,不作逐行改写;后续开发已推进多项内容,部分条目不再代表现状,见下方「当前实现索引」。 ## 当前实现索引(2026-10-14 补记,非原规格内容) - **视觉**:深色玻璃主题为当前默认外观(无主题切换),设计 tokens 见 `src/app/globals.css`;第 8.1 节的「CSS 低饱和青蓝渐变背景」已由可替换的背景素材层 + 深色遮罩取代,样式由现有 CSS 管理,素材类型/路径/画面不作设计规范。 - **移动端**:第 2.2 节「本次不实现」中的「移动端与平板布局」已完成——≤767px 时侧边栏变为顶部横栏,其余断点适配见两份设计文档的第 0 章;「深色模式」以深色玻璃默认主题的形式落地(无切换开关)。 - **数据**:第 7.1 节的「7 个分类 / 20 条链接 / 8 条 pinned」为当时规模;当前为 8 个分类、51 条链接、6 条 pinned。 - **分类页**:第 2.2 节「正式分类页」与第 3 节「分类导航临时禁用」已被后续工作取代——分类页已在 `/categories/[categoryId]` 实现并启用导航。 - **设置**:`settings.theme`、`settings.background` 仍仅有数据模型与校验,不驱动样式;`settings.search` 与 `settings.sidebar` 已接入。 - 其余章节(目录结构、交互、测试与验收)保留原貌,作为首页阶段的历史验收依据。 ## 1. 背景与目标 Polaris 是一个轻量、安静、高效的个人浏览器起始页,不是后台管理系统或信息密集型 Dashboard。 本次工作只建立推荐的项目结构并实现完整可交互首页。用户验收首页视觉和交互后,才会明确决定是否继续实现分类页及第一阶段其余目标。 ## 2. 范围 ### 2.1 本次实现 - 初始化 Next.js App Router 项目; - 建立 Feature-first 混合目录; - 整理现有设计文档和资产位置; - 实现低饱和青蓝 CSS 渐变背景与浅色玻璃视觉; - 实现可折叠且持久化的桌面侧栏; - 实现实时时钟、日期和分时问候语; - 实现 Google、Bing、GitHub、Local 四种严格分离的搜索模式; - 实现 Local 搜索结果、键盘导航和空状态; - 实现 8 个常用链接; - 实现统一图标 fallback; - 建立本地 JSON、service、类型与数据校验边界; - 完成单元测试、组件交互测试、静态构建和真实页面视觉检查。 ### 2.2 本次不实现 - 正式分类页; - Go API 与 PostgreSQL; - 管理页或隐藏管理入口; - 深色模式; - 移动端与平板布局; - 自定义背景上传; - 自定义搜索引擎、URL 直达、AI 搜索; - 加载骨架和后端错误重试。 ## 3. 已确认决策 | 决策项 | 结论 | | ---------- | ----------------------------------------------------------------- | | 首页范围 | 完整可交互首页 | | 目录方案 | Feature-first + 共享基础层 | | 分支粒度 | 项目骨架与首页位于同一个 `feat/polaris-homepage` 分支 | | 文档整理 | 三份文档迁入 `docs/design/`,设计图迁入 `docs/assets/` | | Logo | 现有占位 Logo 迁入 `public/brand/`,不追加品牌设计 | | 运行时背景 | CSS 低饱和青蓝渐变;设计图只作构图参考 | | 外部搜索 | Google、Bing、GitHub 在当前标签页跳转 | | 本地链接 | Local 结果与常用链接在新标签页打开 | | 排序模型 | `sort` 管理分类内顺序;可选 `pinnedSort` 独立管理首页常用链接顺序 | | 分类导航 | 首页验收阶段展示但禁用,避免进入未实现路由;分类页完成后启用 | | 注释 | 关键规则、非显然决策、边界条件和阶段性限制使用简洁中文注释 | | 提交时机 | 分支全部目标完成并验证后再提交 | ## 4. 技术基线 - Next.js App Router; - React; - TypeScript strict; - Tailwind CSS v4,使用 CSS-first 配置; - shadcn/ui,只引入实际使用的基础原语; - lucide-react; - simple-icons; - pnpm; - Vitest; - Testing Library; - ESLint; - Prettier。 `next.config.ts` 使用 `output: "export"`,构建产物必须能作为纯静态文件部署。 不引入 Redux、Zustand、React Query、Zod、Storybook、Repository interface 或依赖注入容器。当前规模下这些抽象没有必要。 ## 5. 推荐目录 ```text polaris/ ├─ docs/ │ ├─ design/ │ │ ├─ Polaris 项目说明文档.md │ │ ├─ Polaris 首页设计文档.md │ │ ├─ Polaris 分类页设计文档.md │ │ └─ 2026-08-26-Polaris-首页实现规格.md │ └─ assets/ │ └─ Polaris页面设计图.png ├─ public/ │ ├─ brand/ │ │ └─ logo.svg │ └─ icons/ ├─ src/ │ ├─ app/ │ │ ├─ globals.css │ │ ├─ layout.tsx │ │ └─ page.tsx │ ├─ components/ │ │ ├─ ui/ │ │ └─ shared/ │ ├─ features/ │ │ ├─ home/ │ │ │ ├─ components/ │ │ │ ├─ hooks/ │ │ │ └─ lib/ │ │ └─ navigation/ │ │ └─ components/ │ ├─ data/ │ │ ├─ categories.json │ │ ├─ links.json │ │ └─ settings.json │ ├─ services/ │ │ ├─ categories.ts │ │ ├─ links.ts │ │ └─ settings.ts │ ├─ types/ │ │ ├─ navigation.ts │ │ ├─ search.ts │ │ └─ settings.ts │ ├─ lib/ │ │ └─ utils.ts │ └─ test/ │ └─ setup.ts ├─ components.json ├─ eslint.config.mjs ├─ next.config.ts ├─ package.json ├─ pnpm-lock.yaml ├─ postcss.config.mjs ├─ prettier.config.mjs ├─ tsconfig.json └─ vitest.config.ts ``` ### 5.1 目录职责 - `app/`:路由、metadata、根布局、全局样式和页面组合; - `features/`:按功能聚合组件、hooks 和专属纯函数; - `components/ui/`:shadcn/ui 基础原语; - `components/shared/`:跨功能复用组件,例如 `GlassPanel`、`SiteIcon`; - `data/`:第一版本地 JSON; - `services/`:页面与数据源之间的唯一访问边界; - `types/`:跨功能共享的数据契约; - `lib/`:真正通用且不依赖业务功能的工具; - `test/`:全局测试环境与共享测试辅助代码。 测试文件与被测试实现就近放置,`src/test/` 不作为所有测试的集中目录。 ## 6. 渲染与组件边界 `src/app/page.tsx` 保持为 Server Component,在构建时调用 service 层读取数据,再将可序列化数据传给页面组件。 ```text app/page.tsx └─ AppShell ├─ Sidebar │ ├─ SidebarHeader │ └─ SidebarNav └─ HomeContent ├─ ClockGreeting ├─ SearchBox │ ├─ SearchProviderMenu │ └─ LocalSearchResults └─ QuickLinks └─ QuickLinkCard └─ SiteIcon ``` Client Component 边界仅用于浏览器状态: - `AppShell`:侧栏折叠与持久化; - `ClockGreeting`:当前时间; - `SearchBox`:Provider、query、结果选中项和快捷键; - `SiteIcon`:图标加载失败后的 fallback。 把 Server Component 作为 `AppShell` 的 `children` 传入,不因为外层存在 Client Component 就将全部展示代码改成客户端代码。 不为每个小标签或图标创建组件。只有存在独立状态、复用价值或清晰职责时才拆分。 ## 7. 数据模型与数据流 ### 7.1 数据规模 - 7 个分类; - 开发工具 15 条; - ChatGPT、Gmail、Notion、YouTube、Bilibili 5 条跨分类链接; - 共 20 条链接; - 8 条 `pinned` 常用链接; - `sort` 只表示分类内顺序,可选 `pinnedSort` 只表示首页常用链接顺序。 首页常用链接固定按以下 `pinnedSort` 顺序展示:GitHub、ChatGPT、Gmail、Notion、Figma、YouTube、Vercel、Bilibili。该字段解决开发工具分类中 Vercel 位于 Figma 之前、但首页中 Figma 位于 Vercel 之前的双重排序需求。 设计资源、学习资料、生活服务保持无链接;AI 工具、效率工具和娱乐媒体因常用链接而有少量数据。 ### 7.2 访问路径 1. `page.tsx` 调用 `getCategories()`、`getLinks()` 和 `getSettings()`; 2. service 层复制、排序和筛选数据; 3. 页面不直接 import JSON; 4. 首页只把所需的分类、全部搜索候选、8 条常用链接和设置传给客户端; 5. 未来接 Go API 时保持 service 的对外职责不变,只替换其内部数据来源。 ### 7.3 数据校验 Vitest 必须校验: - 必填字段类型; - 分类和链接 ID 唯一; - URL 合法; - `categoryId` 必须指向现有分类; - `sort` 与可选 `pinnedSort` 合法; - Provider 配置有效; - `pinned` 数量为 8,且每条 pinned 链接都有唯一 `pinnedSort`。 ## 8. 视觉系统 ### 8.1 背景 使用多层 CSS 渐变和柔和光斑模拟晨雾、天空和水面氛围,不把 `Polaris页面设计图.png` 作为运行时背景。 增加浅色遮罩和适度模糊,保证未来替换背景时仍具有可读性。 ### 8.2 布局 - 侧栏外边距:`20px`; - 展开宽度:`240px`; - 折叠宽度:`72px`; - 主内容视觉重心位于中上方; - 使用 `minmax()` 与 `clamp()` 适配 1024px 以上桌面窗口; - 搜索框最大宽度约 `760px`、高度约 `68px`; - 常用链接最大四列、共两行,缩窄窗口时允许减少列数; - 第一版不实现移动端抽屉侧栏。 ### 8.3 Design tokens 在 `globals.css` 中定义 Polaris 语义变量,并通过 Tailwind v4 `@theme inline` 映射: - 主文本、次级文本、弱文本; - 主色、hover 色、柔和主色; - 侧栏、搜索框、卡片表面; - 玻璃边框; - 软阴影、中等阴影; - 圆角与模糊强度。 第一版只实现浅色值,但变量命名允许以后通过主题选择器覆盖。 ### 8.4 动效与无障碍 - hover、focus 和折叠过渡约为 180ms; - 不使用夸张缩放或弹跳; - 尊重 `prefers-reduced-motion`; - 所有图标按钮有可访问名称; - 键盘焦点清晰; - 搜索输入与结果使用合理的 combobox/listbox 语义; - 禁用分类项使用 `aria-disabled` 并提供“分类页将在下一阶段提供”的轻量提示。 ## 9. 首页交互 ### 9.1 时钟与问候 问候规则: ```text 05:00 - 11:59 早上好 12:00 - 13:59 中午好 14:00 - 17:59 下午好 18:00 - 23:59 晚上好 00:00 - 04:59 夜深了 ``` 客户端按分钟更新;跨午夜后日期、星期和问候语同步变化。 ### 9.2 Provider 状态 - 初始值来自 `settings.search.defaultProvider`; - 用户切换后保存到 `localStorage`; - 无效值或存储不可用时回退到设置默认值; - 外部 Provider 与 Local 模式严格分离。 ### 9.3 外部搜索 Google、Bing、GitHub: - 输入先 `trim()`; - 空内容不提交; - 使用 `URLSearchParams` 编码; - 在当前标签页跳转; - 不显示 Local 结果下拉。 ### 9.4 Local 搜索 匹配字段: - 链接标题; - 关键词; - 描述; - 分类名称; - 分类 ID。 优先级: ```text 标题完全匹配 标题前缀或包含 关键词匹配 描述匹配 分类匹配 ``` 同分时按链接 `sort` 和标题稳定排序,最多返回 8 条。 交互: - 默认选中第一项; - `↑` / `↓` 移动; - `Enter` 打开选中项; - 鼠标点击直接打开; - 结果在新标签页打开; - `Esc` 清空输入并关闭面板; - 无结果显示“没有匹配的网站”。 ### 9.5 快捷键 - 首页加载后自动聚焦搜索框; - Windows/Linux:`Ctrl+K`; - macOS:`⌘K`; - 快捷键阻止浏览器默认行为后聚焦; - 监听器只存在于首页生命周期内。 ### 9.6 侧栏 - 折叠状态保存到 `localStorage`; - 存储不可用或值损坏时回退到设置默认值; - hydration 后同步持久化值; - 同步完成前禁用布局过渡,减少闪动; - 首页项为当前选中态; - 分类项本次显示但禁用,不进入 404。 ### 9.7 常用链接 - 对 `pinned` 链接按 `pinnedSort` 排序取前 8 条; - `pinned: true` 时必须提供唯一的 `pinnedSort`; - 使用真实 ``; - `target="_blank"`; - `rel="noopener noreferrer"`; - 卡片仅显示图标与名称。 ## 10. 图标策略 运行时优先级: ```text 自定义本地图标 明确映射的 Simple Icons favicon 标题首字符文字占位 ``` 当前 20 条数据尽量命中显式 Simple Icons 映射,避免正常页面依赖远程 favicon。远程图标失败时不得显示破图。 侧栏分类图标使用 lucide-react,不参与站点图标 fallback。 ## 11. 状态与故障处理 - 静态 JSON 无 Loading UI; - 数据结构错误由测试和构建阶段阻止; - `localStorage` 读写使用容错封装; - favicon 失败显示文字占位; - 空搜索不跳转; - Local 无结果显示轻量空状态; - 非法 Provider 回退到配置默认值; - Server 初始值与 Client 首次渲染保持一致,避免 hydration error。 ## 12. 注释规范 必须为以下非显然逻辑添加简洁中文注释,解释“为什么”: - Local 搜索权重及稳定排序; - Server/Client 边界选择; - 图标 fallback 顺序; - hydration 后同步 `localStorage`; - 分类导航临时禁用; - 数据校验中的跨分类引用规则。 普通 JSX、显然的变量赋值和框架惯用代码不添加复述式注释。 ## 13. 测试设计 ### 13.1 纯函数 - 问候语全部时间边界; - 日期格式; - 搜索 URL 与特殊字符编码; - Local 搜索字段、权重、排序和数量上限; - 图标引用解析; - 数据结构及交叉引用。 ### 13.2 组件交互 - 自动聚焦; - Provider 严格分离; - Provider 持久化与非法值回退; - Local 键盘导航、Enter、Esc、点击和无结果; - `Ctrl+K` / `⌘K`; - 侧栏折叠、恢复和异常存储回退; - 新标签页链接安全属性; - 分类项禁用状态。 ## 14. 验证与视觉验收 完成后必须运行: ```text pnpm lint pnpm typecheck pnpm test pnpm build ``` 随后实际启动页面并检查: - `1440 × 900`; - `1920 × 1080`; - `1024 × 768`。 检查项: - 无横向溢出; - 背景渐变层次和文本对比度; - 玻璃透明度、边框与阴影一致; - 侧栏展开/折叠比例; - 搜索聚焦与 Provider 交互; - Local 结果定位和键盘交互; - 卡片排列、hover 和 focus; - `prefers-reduced-motion`; - 控制台无 hydration error 或运行时错误。 设计图用于构图和氛围参照,不要求像素级复制图片背景。 ## 15. 完成定义 只有以下条件全部满足,`feat/polaris-homepage` 才完成: 1. 推荐目录和工程配置建立; 2. 文档及资产迁移完成,内部链接有效; 3. 完整可交互首页实现; 4. 自动化检查全部通过; 5. 真实页面完成三个尺寸的视觉检查; 6. 实际结果、视觉观察和已知偏差如实报告; 7. 未提前实现正式分类页; 8. 完成后再执行提交操作。