feat(ts): 实现 Polaris 可交互首页

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-02 23:53:28 +08:00
co-authored by Claude Opus 4.8
parent 95c3da0f71
commit c3d2a40c1f
71 changed files with 15613 additions and 46 deletions
@@ -0,0 +1,453 @@
# Polaris 首页实现规格
> 日期:2026-08-26
>
> 状态:用户已批准,可进入实施
>
> 实现分支:`feat/polaris-homepage`
## 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. 完成后再执行提交操作。
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,433 @@
# Polaris 项目说明文档
> 关联文档:
> [Polaris 首页设计文档](./Polaris%20首页设计文档.md) —— 首页布局、搜索交互、视觉规范
> [Polaris 分类页设计文档](./Polaris%20分类页设计文档.md) —— 分类页布局、卡片、状态设计
>
> 配套资产:[占位 Logo](../../public/brand/logo.svg)(后续替换)、[Polaris 页面设计图](../assets/Polaris页面设计图.png)
## 1. 项目名称
**Polaris**
Polaris 原意为“北极星”,具有方向、导航、定位、指引的含义。该名称与个人导航站的产品定位非常契合。
本项目的核心目标是打造一个轻量、安静、高效、可长期维护的个人导航站,用于集中管理和访问常用网站、分类资源与个人入口。
---
## 2. 项目定位
Polaris 不是后台管理系统,也不是信息密集型 Dashboard。
> 一个具有个人审美、可自定义背景、可快速搜索和访问常用链接的浏览器起始页。
产品形态是**动态可配置的个人导航站**:第一版可以静态部署,数据写死在本地配置中,但页面具有动态交互(实时时钟、搜索、侧边栏折叠、快捷键等),后续将本地数据源替换为 Go 后端 API,不需要推翻前端整体结构。
---
## 3. 项目目标
第一阶段目标:
1. 提供一个美观的个人导航首页;
2. 支持快速访问常用链接;
3. 支持左侧分类导航与分类页;
4. 支持搜索框(Google / Bing / GitHub / 本地);
5. 支持当前时间与日期展示;
6. 支持左侧边栏折叠(状态持久化);
7. 支持背景自定义的设计预留;
8. 数据第一版写死在本地 JSON 数据配置中;
9. 代码结构预留未来接入 Go 后端接口的能力。
后续阶段目标:
1. 从 Go 后端 API 获取链接、分类、设置等数据;
2. 使用数据库持久化配置;
3. 支持后台或隐藏入口管理链接;
4. 支持用户自定义背景;
5. 支持主题色、深色模式、导入导出、移动端适配等个性化能力。
---
## 4. 已确认的关键决策
以下决策已于 2026-08-21 确认,作为实施基线:
| # | 决策项 | 结论 |
| --- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| 1 | 技术栈 | 以本文档为准:Next.js + TypeScript + Tailwind CSS + shadcn/ui + lucide-react(早期 README 中 Astro 设想作废) |
| 2 | 渲染策略 | 静态导出(`output: 'export'`),时钟/搜索/侧边栏折叠为 client 岛屿 |
| 3 | 包管理器 | pnpm |
| 4 | 项目位置 | `ts/web/nextjs/polaris/` |
| 5 | Logo | 现有 `logo.svg` 仅作占位,后续替换,不投入设计精力 |
| 6 | 数据载体 | 本地 JSON 数据文件(`links.json` / `categories.json` / `settings.json`),结构以 Vitest 校验测试兜底 |
| 7 | 第一版数据范围 | 「开发工具」15 条 + 首页常用链接 8 条(跨分类条目归入真实分类,见首页设计文档 8.1);设计资源、学习资料、生活服务三个分类展示空状态 |
| 8 | 默认背景 | CSS 低饱和青蓝渐变,不依赖图片资产;`settings.background.type` 预留 `image` |
| 9 | 深色模式 | 第一版不做;CSS 变量按双主题友好组织,后续以 `[data-theme="dark"]` 覆盖 |
| 10 | 移动端 | 第一版不适配,仅桌面端 |
| 11 | 快捷键 | Ctrl+K(macOS ⌘K)聚焦搜索框,仅首页生效;提示按平台检测显示 |
| 12 | 图标方案 | 四级 fallback:自定义图标 → Simple Icons / 本地图标库 → favicon 自动抓取 → 文字占位(见 6.4) |
| 13 | 后端数据库 | PostgreSQL(后续阶段);表结构不锁死 PG 特有语法(JSON 存通用 TEXT) |
| 14 | 搜索行为 | 按模式严格分离:选中哪个引擎就只用哪个引擎搜索(回车直跳);仅 Local 模式显示本地结果下拉 |
---
## 5. 技术选型
### 5.1 前端技术栈
```text
Next.js(App Router)
TypeScript
Tailwind CSS
shadcn/ui
lucide-react
simple-icons
```
说明:
1. **Next.js**:适合从静态页面起步,后续接入动态接口;
2. **TypeScript**:适合维护链接、分类、配置等结构化数据;
3. **Tailwind CSS**:适合快速实现玻璃感、响应式、主题变量;
4. **shadcn/ui**:用于基础组件,例如 Button、Input、Dialog、Command;
5. **lucide-react**:用于侧边栏和操作图标;
6. **simple-icons**:品牌图标库(npm 包,按 slug 引用)。
测试:Vitest + Testing Library,重点覆盖纯函数(问候语分段、本地搜索匹配排序、icon 解析、搜索 URL 模板)与核心交互组件。
### 5.2 渲染策略
采用静态导出:
```ts
// next.config.ts
const nextConfig = { output: "export" }
```
- 首页与全部分类页在构建时从本地数据生成(`generateStaticParams`);
- 时钟、搜索、侧边栏折叠等交互以 client 组件实现;
- 产物为纯静态文件,可部署到任意静态服务器;
- 后续接 Go 后端时,改造 service 层为 fetch,数据页面改为客户端获取。
### 5.3 数据访问层
**页面组件不直接依赖数据文件,一律通过 service 层获取数据。**
第一版:
```ts
import links from "@/data/links.json"
export function getLinks(): NavLink[] {
return links
}
```
后续接 Go 后端时,只需要修改 service 层:
```ts
export async function getLinks() {
const res = await fetch("/api/links")
if (!res.ok) {
throw new Error("Failed to fetch links")
}
return res.json()
}
```
这样页面组件不用大规模重构。
JSON 缺少编译期类型检查,用 **Vitest 数据校验测试**兜底:校验 `data/*.json` 满足 `Category[]` / `NavLink[]` / `AppSettings` 结构(必填字段齐全、类型正确、categoryId 存在于分类中),字段拼错或漏填在测试阶段即报错。
### 5.4 后端技术栈(后续阶段)
后端由 Go 实现,数据库 PostgreSQL。
候选组合:
```text
Go + Chi + sqlc —— 结构清晰、SQL 可控(推荐)
Go + Gin + GORM —— 开发更快、上手成本低
```
---
## 6. 数据结构
### 6.1 分类数据
```ts
{
id: "dev",
title: "开发工具",
description: "高效的开发工具和资源集合,提升你的开发体验和效率。",
icon: "code",
sort: 20
}
```
字段说明:
```text
id: 分类唯一标识
title: 分类名称
description: 分类描述(分类页标题下方展示)
icon: 图标名称(侧边栏,lucide 图标名)
sort: 排序值
```
### 6.2 链接数据
```ts
{
id: "github",
title: "GitHub",
description: "全球最大的代码托管平台",
url: "https://github.com",
icon: "si:github",
categoryId: "dev",
pinned: true,
pinnedSort: 10,
keywords: ["代码", "仓库", "开源", "git"],
sort: 10
}
```
字段说明:
```text
id: 链接唯一标识
title: 链接名称
description: 简短描述(分类页卡片展示,8-16 个中文字)
url: 链接地址
icon: 图标引用,格式见 6.4
categoryId: 所属分类
pinned: 是否展示在首页常用链接区域
pinnedSort: 首页常用链接顺序(仅 pinned 为 true 时必填)
keywords: 本地搜索关键词
sort: 分类内排序值
```
### 6.3 设置数据
```ts
{
siteName: "Polaris",
theme: "light-glass",
search: {
defaultProvider: "google",
enabledProviders: ["google", "bing", "github", "local"]
},
background: {
type: "gradient", // "gradient" | "image"
url: null, // type 为 image 时生效
blur: 8,
overlay: 0.32
},
sidebar: {
defaultCollapsed: false
}
}
```
### 6.4 统一图标方案
链接卡片与搜索结果的图标按以下顺序解析,逐级 fallback:
```text
1. 自定义图标 icon: "local:xxx" → public/icons/xxx.svg
2. Simple Icons icon: "si:github" → simple-icons npm 包按 slug 渲染品牌图标
3. favicon 抓取 icon: "url:https://..." → 远程图标;icon 缺省时按链接 url 域名自动抓取
4. 文字占位 以上全部失败 → 标题首字符 + 圆角玻璃底兜底
```
侧边栏分类导航图标使用 lucide-react(如 `home` / `code` / `palette`),不参与此 fallback 链。
---
## 7. 未来 Go 后端 API 设计
后续接入 Go 后端时,推荐 API:
```text
GET /api/categories
GET /api/links
GET /api/links?categoryId=dev
GET /api/settings
```
后续管理能力:
```text
POST /api/links
PUT /api/links/:id
DELETE /api/links/:id
POST /api/categories
PUT /api/categories/:id
DELETE /api/categories/:id
PUT /api/settings
```
背景相关:
```text
POST /api/backgrounds
GET /api/backgrounds
PUT /api/settings/background
```
---
## 8. 数据库表设计草案
> 草案使用跨数据库通用语法;JSON 以 TEXT 存储。PostgreSQL 下可将 `TEXT`(存 JSON)字段替换为 `JSONB` 以获得索引与查询能力。
### 8.1 categories
```sql
CREATE TABLE categories (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
icon TEXT NOT NULL,
sort INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```
### 8.2 links
```sql
CREATE TABLE links (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
url TEXT NOT NULL,
icon TEXT,
category_id TEXT NOT NULL,
pinned BOOLEAN NOT NULL DEFAULT FALSE,
pinned_sort INTEGER,
keywords TEXT NOT NULL DEFAULT '[]', -- JSON 数组字符串
sort INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_links_category
FOREIGN KEY (category_id)
REFERENCES categories(id)
);
```
### 8.3 settings
```sql
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL, -- JSON 字符串
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```
---
## 9. 开发阶段
### 第一阶段:前端静态版(当前阶段)
目标:完成首页与分类页基础体验。
```text
页面背景(CSS 渐变)与背景遮罩
左侧边栏 + 折叠(localStorage 持久化)+ 导航选中态
时间 / 日期 / 问候语
搜索框(Google / Bing / GitHub / 本地搜索)+ Ctrl+K 快捷键
常用链接(pinned,8 个)
分类页路由 + 卡片网格 + 空状态
桌面端响应式
```
数据范围:开发工具 15 个链接 + 首页 8 个常用链接(跨分类条目归入真实分类:ChatGPT → AI 工具,Gmail / Notion → 效率工具,YouTube / Bilibili → 娱乐媒体);设计资源、学习资料、生活服务三个分类展示空状态。
### 第二阶段:数据补全与体验完善
```text
补全其余 6 个分类的链接数据
深色玻璃主题
移动端 / 平板适配
```
### 第三阶段:数据访问层抽象
```text
封装 links / categories / settings service
统一 TypeScript 类型
统一错误处理与 loading 状态
```
此阶段完成后,页面组件不直接关心数据来自本地配置还是 API。
### 第四阶段:Go 后端 API
```text
Go API 服务
数据库连接(PostgreSQL)
links / categories / settings 查询接口
基础 CORS 配置
Docker 部署预留
```
### 第五阶段:管理能力
```text
新增 / 编辑 / 删除链接
调整排序、修改分类
修改背景、主题
导入导出配置
```
管理能力不进入首页主视觉,放到独立管理页或隐藏入口。
---
## 10. 项目目录结构
```text
polaris/
public/
icons/ # 自定义 SVG 图标(icon: "local:xxx" 引用)
logo.svg
src/
app/
layout.tsx # 背景层 + 字体
page.tsx # 首页
categories/
[categoryId]/
page.tsx # 分类页(generateStaticParams 生成)
globals.css # Tailwind + CSS 变量(浅色值,深色预留覆盖位)
components/
layout/ # AppShell / Sidebar / SidebarNavItem
home/ # ClockGreeting / SearchBox / SearchResults / QuickLinks
category/ # CategoryHeader / ToolGrid / ToolCard
common/ # SiteIcon(四级 fallback)/ GlassPanel
data/ # links.json / categories.json / settings.json
services/ # links.ts / categories.ts / settings.ts / search.ts
types/ # navigation.ts / settings.ts / search.ts
lib/ # cn.ts / greeting.ts / local-search.ts / search-providers.ts / icon.ts(纯函数)
next.config.ts # output: 'export'
vitest.config.ts
```
---
## 11. 最终目标
> 打开 Polaris 后,用户可以立刻搜索、立刻访问常用网站,也可以通过侧边栏进入分类页面。页面具有个人氛围,但保持干净、高效、克制。第一版数据本地配置,后续平滑切换到 Go 后端。
@@ -0,0 +1,780 @@
# Polaris 首页设计文档
> 关联文档:
> [Polaris 项目说明文档](./Polaris%20项目说明文档.md) —— 项目定位、技术选型、数据结构、开发阶段
> [Polaris 分类页设计文档](./Polaris%20分类页设计文档.md) —— 分类页布局与交互
>
> 配套设计图:[Polaris 页面设计图](../assets/Polaris页面设计图.png)(示意图,视觉细节以本文档文字规范为准)
## 1. 页面定位
Polaris 首页是一个**浏览器起始页**,不是后台管理系统,也不是信息密集型 Dashboard。
> 一个具有个人审美、可自定义背景、可快速搜索和访问常用链接的浏览器起始页。
首页应给人的感觉是:
```text
私人入口
浏览器首页
轻量工具启动页
个人网址导航
安静、高效、有氛围感
```
而不是:
```text
后台系统
资源管理平台
复杂书签管理器
信息看板
数据仪表盘
```
---
## 2. 整体视觉风格
首页推荐采用:
> 轻量玻璃感 + 极简浏览器起始页 + 柔和个人空间感
设计关键词:
```text
极简
半透明
玻璃感
柔和阴影
大留白
低饱和
可换背景
个人化
安静
高级感
```
整体视觉应避免过重的管理系统风格。
首页不应常驻展示以下元素:
```text
设置按钮
编辑按钮
管理按钮
新增按钮
统计数据
用户头像
访问次数
站点域名副标题
复杂分类卡片
```
这些能力在后续阶段通过隐藏入口、独立配置页或快捷键面板实现。
---
## 3. 首页整体布局
首页采用左右结构:
```text
┌──────────────────┬────────────────────────────────────────────┐
│ 左侧边栏 │ 主内容区域 │
│ │ │
│ Logo + 折叠按钮 │ 问候语 + 当前时间 │
│ 首页 │ 日期 / 星期 │
│ AI 工具 │ 搜索框 │
│ 开发工具 │ 常用链接 │
│ 设计资源 │ │
│ 学习资料 │ │
│ 效率工具 │ │
│ 娱乐媒体 │ │
│ 生活服务 │ │
└──────────────────┴────────────────────────────────────────────┘
```
布局原则:
1. 左侧边栏负责分类导航;
2. 主内容区域只展示最高频内容;
3. 首页不展示分类卡片;
4. 首页不展示编辑、设置、管理类按钮;
5. 常用链接只做轻量展示。
主内容整体居中,视觉重心位于页面中上方。
推荐信息层级:
```text
1. 当前时间 / 问候语
2. 搜索框
3. 常用链接
4. 左侧分类导航
5. 背景氛围
```
---
## 4. 左侧边栏设计
### 4.1 边栏内容
左侧边栏从上到下依次展示:
```text
Logo
折叠按钮
首页
AI 工具
开发工具
设计资源
学习资料
效率工具
娱乐媒体
生活服务
```
「首页」指向 `/`,各分类指向 `/categories/{categoryId}`。当前路由对应的导航项处于选中态,选中样式见[分类页设计文档](./Polaris%20分类页设计文档.md) 6.2 节。
暂时不展示:
```text
设置
关于
编辑
管理
新增分类
账户信息
```
### 4.2 折叠按钮位置
折叠按钮放在侧边栏顶部右侧,与 Logo 位于同一行。
示意:
```text
┌──────────────────────┐
│ Logo ≪ │
└──────────────────────┘
```
不采用侧边栏中间悬浮按钮。
原因:
1. 中间悬浮按钮视觉上突兀;
2. 容易产生后台管理系统的感觉;
3. 会破坏页面边界的干净感;
4. 顶部右侧更符合用户对全局控制按钮的认知。
### 4.3 边栏宽度
展开状态:
```text
推荐宽度:240px
可选范围:220px - 260px
```
折叠状态:
```text
推荐宽度:72px
可选范围:68px - 80px
```
展开时显示图标和文字。
折叠时只显示图标。
折叠状态持久化到 `localStorage`,刷新后保持。
### 4.4 边栏视觉样式
推荐样式:
```css
background: rgba(255, 255, 255, 0.68);
backdrop-filter: blur(24px);
border: 1px solid rgba(255, 255, 255, 0.45);
box-shadow: 0 24px 80px rgba(15, 23, 42, 0.1);
border-radius: 24px;
```
边栏与页面边缘保持一定距离,形成悬浮玻璃面板感。
推荐边距:
```text
left: 20px;
top: 20px;
bottom: 20px;
```
---
## 5. 时间与问候语
首页顶部展示问候语和当前时间。
示例:
```text
晚上好,21:56
2026 年 8 月 20 日 星期四
```
问候语规则:
```text
05:00 - 11:59 早上好
12:00 - 13:59 中午好
14:00 - 17:59 下午好
18:00 - 23:59 晚上好
00:00 - 04:59 夜深了
```
推荐样式:
```css
font-size: 56px;
font-weight: 700;
color: #0f172a;
```
日期样式:
```css
font-size: 16px;
color: #64748b;
```
时间为客户端实时时钟,每分钟更新。
---
## 6. 搜索框设计
### 6.1 搜索框定位
搜索框是 Polaris 首页最核心的交互组件。
搜索框位于时间与日期下方,居中展示,首页加载后**自动聚焦**。
### 6.2 搜索范围
首页搜索框支持以下四种搜索方式:
```text
Google 搜索
Bing 搜索
GitHub 搜索
本地搜索
```
其中:
1. **Google 搜索**:使用 Google 搜索引擎搜索输入内容;
2. **Bing 搜索**:使用 Bing 搜索引擎搜索输入内容;
3. **GitHub 搜索**:在 GitHub 中搜索输入内容;
4. **本地搜索**:搜索 Polaris 本地配置中的链接、分类和名称。
第一版不支持:
```text
直接输入 URL 跳转
自定义搜索引擎
多搜索引擎聚合结果
AI 搜索
站外爬取搜索
全文索引搜索
```
这些能力作为后续扩展。
### 6.3 搜索框尺寸
桌面端推荐:
```text
width: 760px;
height: 68px;
```
可选范围:
```text
宽度:720px - 860px
高度:64px - 72px
```
> 移动端尺寸第一版不定义,移动端整体适配为后续阶段工作。
### 6.4 搜索框视觉样式
推荐样式:
```css
background: rgba(255, 255, 255, 0.72);
backdrop-filter: blur(22px);
border: 1px solid rgba(255, 255, 255, 0.65);
border-radius: 24px;
box-shadow: 0 20px 60px rgba(15, 23, 42, 0.12);
```
placeholder 统一为:
```text
搜索网站、书签或关键词...
```
右侧展示快捷键提示,**按平台检测显示**:
```text
macOS:⌘K
Windows / Linux:Ctrl K
```
快捷键行为:按下后聚焦搜索框(详见 7.4)。
### 6.5 搜索模式切换
搜索模式可以通过以下方式切换:
```text
搜索框左侧下拉选择
快捷键切换
命令面板中选择
默认使用上一次选择的搜索方式
```
第一版采用简单方案:
```text
搜索框左侧或右侧提供当前搜索模式标识
点击后可切换 Google / Bing / GitHub / 本地
```
### 6.6 本地搜索范围
本地搜索的数据来自本地数据配置。
搜索范围包括:
```text
链接标题
链接关键词
链接描述
分类名称
分类 ID
```
链接数据预留 `keywords` 字段,完整数据结构见[项目说明文档](./Polaris%20项目说明文档.md)。
---
## 7. 搜索功能设计
### 7.1 搜索 Provider
支持四个 provider:
```ts
export type SearchProvider = "google" | "bing" | "github" | "local"
```
### 7.2 搜索行为
Google:
```text
https://www.google.com/search?q={keyword}
```
Bing:
```text
https://www.bing.com/search?q={keyword}
```
GitHub:
```text
https://github.com/search?q={keyword}
```
Local:
```text
在本地 links / categories 数据中搜索
```
### 7.3 本地搜索匹配规则
本地搜索匹配字段:
```text
title
keywords
categoryId
category title
```
推荐匹配逻辑(权重从高到低):
```text
完全匹配优先
标题匹配其次
关键词匹配再次
分类匹配最后
```
搜索结果展示:
```text
图标
标题
所属分类
```
搜索行为**按模式严格分离**:选择使用哪个搜索引擎,就只用它搜索。
```text
Google / Bing / GitHub 模式:回车直接跳转对应引擎搜索,不显示任何下拉
Local 模式:输入时实时显示本地结果下拉(见 7.5)
```
Local 模式回车行为:
```text
如果只有一个高匹配本地结果,回车打开该链接
如果有多个结果,↑ / ↓ 选择后回车打开选中项(默认选中第一项)
如果没有结果,展示空状态
```
### 7.4 快捷键
```text
Ctrl K(macOS 为 ⌘K):聚焦搜索框
Esc:清空输入 / 关闭结果列表
```
快捷键仅在首页生效,分类页不响应。
---
### 7.5 结果下拉面板(仅 Local 模式)
面板出现在搜索框正下方,与搜索框同宽,间距 8px,视觉上与搜索框衔接为一体。
面板样式:
```css
background: rgba(255, 255, 255, 0.72);
backdrop-filter: blur(22px);
border: 1px solid rgba(255, 255, 255, 0.65);
border-radius: 20px;
box-shadow: 0 20px 60px rgba(15, 23, 42, 0.12);
```
列表项内容:
```text
图标(遵循统一图标方案)
标题
所属分类
```
交互:
```text
↑ / ↓ 在结果间移动选中项
回车打开选中项(新标签页)
Esc 关闭面板
鼠标点击直接打开
选中项背景 rgba(59, 130, 246, 0.10)
```
结果数量上限 8 条;无匹配时面板内展示空状态「没有匹配的网站」。
---
## 8. 常用链接区域
### 8.1 区域定位
常用链接用于展示用户最高频访问的网站。
首页不展示分类卡片,以保持页面干净。
第一版展示 8 个常用链接:
```text
GitHub
ChatGPT
Gmail
Notion
Figma
YouTube
Vercel
Bilibili
```
实际项目中根据个人习惯调整。
数据来源:`pinned: true` 的链接,按 `pinnedSort` 排序取前 8 个;超出 8 个时按 4 列网格换行展示。`sort` 仅用于分类内排序,避免首页顺序与分类页顺序相互约束。
第一版数据构成(8 条 pinned 分布于各分类,归入真实分类):
```text
GitHub → 开发工具(dev)
Vercel → 开发工具(dev)
Figma → 开发工具(dev)
ChatGPT → AI 工具(ai)
Gmail → 效率工具(efficiency)
Notion → 效率工具(efficiency)
YouTube → 娱乐媒体(media)
Bilibili → 娱乐媒体(media)
```
其中 ChatGPT、Gmail、Notion、YouTube、Bilibili 5 条是为首页常用链接补入的跨分类条目。受此影响,AI 工具、效率工具、娱乐媒体三个分类页会展示少量链接而非空状态;纯空状态分类为设计资源、学习资料、生活服务。
### 8.2 链接卡片内容
常用链接卡片只展示:
```text
图标
名称
```
不展示:
```text
域名
描述
编辑按钮
更多按钮
拖拽按钮
访问统计
```
图标来源遵循统一图标方案(见[项目说明文档](./Polaris%20项目说明文档.md)):自定义图标 → Simple Icons / 本地图标库 → favicon 自动抓取 → 文字占位兜底。
### 8.3 链接卡片样式
推荐样式:
```css
background: rgba(255, 255, 255, 0.62);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.56);
border-radius: 20px;
box-shadow: 0 14px 40px rgba(15, 23, 42, 0.1);
```
推荐尺寸:
```text
width: 200px;
height: 76px;
```
推荐布局:
```text
两行四列
```
示意:
```text
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ GitHub │ │ChatGPT │ │ Gmail │ │ Notion │
└────────┘ └────────┘ └────────┘ └────────┘
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ Figma │ │YouTube │ │ Vercel │ │Bilibili│
└────────┘ └────────┘ └────────┘ └────────┘
```
Hover 效果:
```css
transform: translateY(-2px);
background: rgba(255, 255, 255, 0.78);
box-shadow: 0 18px 50px rgba(15, 23, 42, 0.14);
transition: all 180ms ease;
```
点击卡片在新标签页打开链接。
---
## 9. 背景设计
### 9.1 背景类型
Polaris 支持背景自定义的设计预留。
**第一版默认背景为 CSS 低饱和青蓝渐变**(模拟晨雾 / 天空氛围),不依赖图片资产。
后续支持:
```text
静态图片背景
渐变背景
纯色背景
本地上传背景
远程图片背景
深色背景
```
图片背景建议:
```text
湖面
雪山
晨雾
天空
抽象渐变
低对比度风景图
```
背景类型通过 `settings.background.type`(`gradient` / `image`)配置。
### 9.2 背景遮罩
由于用户未来可能更换任意背景,因此需要增加背景遮罩层,保证内容可读。
浅色主题:
```css
background-overlay: rgba(255, 255, 255, 0.32);
backdrop-filter: blur(8px);
```
深色主题(后续阶段预留):
```css
background-overlay: rgba(15, 23, 42, 0.42);
backdrop-filter: blur(8px);
```
---
## 10. 配色系统
默认采用浅色玻璃主题。
推荐 CSS 变量:
```css
:root {
--color-text-primary: #0f172a;
--color-text-secondary: #64748b;
--color-text-muted: #94a3b8;
--color-primary: #3b82f6;
--color-primary-hover: #2563eb;
--color-primary-soft: rgba(59, 130, 246, 0.1);
--color-surface-sidebar: rgba(255, 255, 255, 0.68);
--color-surface-card: rgba(255, 255, 255, 0.62);
--color-surface-search: rgba(255, 255, 255, 0.72);
--color-border-light: rgba(255, 255, 255, 0.56);
--color-border-subtle: rgba(15, 23, 42, 0.08);
--shadow-soft: 0 14px 40px rgba(15, 23, 42, 0.1);
--shadow-medium: 0 20px 60px rgba(15, 23, 42, 0.12);
}
```
变量按双主题友好方式组织:第一版只实现浅色值;深色玻璃主题为后续阶段,届时以 `[data-theme="dark"]` 覆盖同名变量,组件无需改动。
---
## 11. 字体建议
推荐字体栈:
```css
font-family:
Inter,
"SF Pro Display",
"SF Pro Text",
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
"PingFang SC",
"Hiragino Sans GB",
"Microsoft YaHei",
sans-serif;
```
字体原则:
```text
标题清晰
正文轻盈
导航可读
不要使用过度装饰性字体
```
---
## 12. 首页线框
```text
┌──────────────────────┬────────────────────────────────────────────────────┐
│ │ │
│ LOGO ≪ │ │
│ │ │
│ ⌂ 首页 │ │
│ ✦ AI 工具 │ 晚上好,21:56 │
│ <> 开发工具 │ 2026 年 8 月 20 日 星期四 │
│ ◌ 设计资源 │ │
│ 📖 学习资料 │ ┌────────────────────────────────────┐ │
│ ⚡ 效率工具 │ │ 🔍 搜索网站、书签或关键词... Ctrl K │ │
│ ▶ 娱乐媒体 │ └────────────────────────────────────┘ │
│ ☕ 生活服务 │ │
│ │ 常用链接 │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ │ GitHub │ │ChatGPT │ │ Gmail │ │ Notion │ │
│ │ └────────┘ └────────┘ └────────┘ └────────┘ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ │ Figma │ │YouTube │ │ Vercel │ │Bilibili│ │
│ │ └────────┘ └────────┘ └────────┘ └────────┘ │
│ │ │
└──────────────────────┴────────────────────────────────────────────────────┘
```
---
## 13. 设计原则总结
首页遵循以下原则:
```text
首页只放最高频内容
搜索框是核心入口
搜索范围保持克制
常用链接轻量展示
分类入口放在左侧边栏
首页不展示分类卡片
首页不展示编辑和设置按钮
背景可换,但内容必须始终清晰
玻璃感要轻,不要炫技
交互动效要柔和,不要夸张
整体像浏览器起始页,不像后台系统
第一版数据本地配置,后续平滑切换到 Go 后端
第一版仅实现浅色主题、仅适配桌面端
```
最终目标:
> 打开 Polaris 后,用户可以立刻搜索、立刻访问常用网站,也可以通过侧边栏进入分类页面。页面具有个人氛围,但保持干净、高效、克制。