465 lines
15 KiB
Markdown
465 lines
15 KiB
Markdown
# 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`;
|
||
- 使用真实 `<a>`;
|
||
- `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. 完成后再执行提交操作。
|