Files
lab/ts/web/nextjs/polaris/docs/design/Polaris 项目说明文档.md
labmem 86f6eb39b2 docs(polaris): 同步设计与部署文档现状
对齐深色玻璃样式、响应式布局、数据规模与配置生效范围。
明确仅由 polaris-v* 标签触发发布,并保留历史规格说明。
2026-10-14 16:38:07 +08:00

480 lines
20 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.
# Polaris 项目说明文档
> 关联文档:
> [Polaris 首页设计文档](./Polaris%20首页设计文档.md) —— 首页布局、搜索交互、视觉规范
> [Polaris 分类页设计文档](./Polaris%20分类页设计文档.md) —— 分类页布局、卡片、状态设计
>
> 配套资产:[站点 Logo](../../public/brand/logo.svg)、[Polaris 页面设计图](../assets/Polaris页面设计图.png)
> 本文以当前代码实现为准(2026-10-14 对齐)。早期决策中已被后续实现取代的条目在第 4 节单独标注为「历史」,不再代表现状。
## 1. 项目名称
**Polaris**
Polaris 原意为“北极星”,具有方向、导航、定位、指引的含义。该名称与个人导航站的产品定位非常契合。
本项目的核心目标是打造一个轻量、安静、高效、可长期维护的个人导航站,用于集中管理和访问常用网站、分类资源与个人入口。
---
## 2. 项目定位
Polaris 不是后台管理系统,也不是信息密集型 Dashboard。
> 一个具有个人审美、可快速搜索和访问常用链接的浏览器起始页。
产品形态是**个人导航站**:数据内置于本地 JSON 配置、随构建打包,页面具有动态交互(实时时钟、搜索、侧边栏折叠、快捷键等)。页面统一经 service 层取数,不依赖具体数据源形态;后续数据源迁移另行规划。
---
## 3. 项目目标
第一阶段目标(已全部实现):
1. 提供一个美观的个人导航首页;
2. 支持快速访问常用链接;
3. 支持左侧分类导航与分类页;
4. 支持搜索框(Google / Bing / GitHub / 本地);
5. 支持当前时间与日期展示;
6. 支持左侧边栏折叠(状态持久化);
7. 数据内置于本地 JSON 配置,结构由 Vitest 校验测试兜底;
8. 页面组件统一经 service 层取数。
背景自定义目前停留在数据模型与校验预留(`settings.background`),不驱动样式,见 6.3。
后续阶段目标(未实现):
1. 从 Go 后端 API 获取链接、分类、设置等数据;
2. 使用数据库持久化配置;
3. 支持后台或隐藏入口管理链接;
4. 让 `settings.theme` / `settings.background` 真正驱动界面,支持自定义背景与主题切换;
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 | 默认背景 | 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 模式显示本地结果下拉 | 现行 |
历史决策的现行实现:
- **#2 渲染策略**:2026-10-12 部署阶段由静态导出改为 standalone 服务端输出,见 5.2。
- **#7 数据范围**:当前为 8 个分类共 51 条链接,全部分类均有内容,首页常用链接 6 条,见 9。
- **#8 默认背景**:当前视觉由 CSS 定义的深色玻璃表面与背景遮罩构成;背景素材为可替换内容,不作为设计规范,见 5.5。`settings.background` 未驱动样式。
- **#9 深色模式**:当前为固定深色玻璃(`color-scheme: dark`),无双主题切换,见 5.5。
- **#10 移动端**:移动端已适配,侧边栏在窄屏转为顶部横栏,见 5.5。
---
## 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**:配合 CSS 变量实现玻璃表面、响应式与视觉 token;
4. **shadcn/ui**:用于基础组件(Button、DropdownMenu、Tooltip);
5. **lucide-react**:用于侧边栏和操作图标;
6. **simple-icons**:品牌图标库(npm 包,按 slug 引用)。
测试:Vitest + Testing Library,覆盖静态数据结构校验、service 层行为、纯函数(时间格式化与问候语分段、本地搜索匹配排序、icon 候选解析、搜索 URL 模板、偏好读写)与核心交互组件。
### 5.2 渲染策略
采用 standalone 输出:
```ts
// next.config.ts
const nextConfig: NextConfig = {
output: "standalone",
trailingSlash: true,
images: { unoptimized: true },
}
```
- 首页与全部分类页为 server 组件,构建时经 service 层从本地数据生成;分类页通过 `generateStaticParams` 预生成,`dynamicParams = false`(未知分类统一 404);
- 时钟、搜索、侧边栏折叠等交互以 client 组件实现;
- 构建产物为 `.next/standalone` 下的最小 Node 服务器(`server.js`),`public` 与 `.next/static` 需按 Next 文档在部署时手动复制。
### 5.3 数据访问层(已实现)
**页面组件不直接依赖数据文件,一律通过 service 层获取数据。**
当前 service 层已实现,页面与数据文件解耦:
```ts
// src/services/links.ts
export function getLinks(): NavLink[]
export function getLinksByCategory(categoryId: string): NavLink[] // 按 sort 排序
export function getPinnedLinks(limit = 8): NavLink[] // 按 pinnedSort 排序
// src/services/categories.ts
export function getCategories(): Category[]
export function getCategoryById(id: string): Category | null
// src/services/settings.ts
export function getSettings(): AppSettings // 解析时校验,非法数据直接抛错
```
所有函数返回数据副本,避免调用方改动模块级数据。`settings.json` 在模块加载时经 `parseSettings` 逐字段校验,类型不符直接抛 `TypeError`,问题在构建/测试阶段即暴露。
数据源切换的迁移方案另行规划;当前已确保页面组件只依赖 service 接口,不直接依赖数据文件。
JSON 缺少编译期类型检查,用 **Vitest 数据校验测试**兜底:`src/data/data.test.ts` 校验 `data/*.json` 满足 `Category[]` / `NavLink[]` / `AppSettings` 结构(必填字段齐全、类型正确、categoryId 存在于分类中、URL 唯一且不含会话参数等),`src/services/services.test.ts` 校验 service 行为。
### 5.4 后端技术栈(未来阶段)
计划由 Go 实现,数据库选用 PostgreSQL(决策 #13,未实施)。
候选组合:
```text
Go + Chi + sqlc —— 结构清晰、SQL 可控(推荐)
Go + Gin + GORM —— 开发更快、上手成本低
```
### 5.5 视觉与布局现状
界面为**固定深色玻璃**风格,不提供主题切换。视觉描述以 `src/app/globals.css` 中的设计 token 与表面为准:
- **全局基调**:`:root` 声明 `color-scheme: dark`,底色为深蓝夜色(`--background: #101a2a`),文字为亮色系(`--text-primary` / `--text-secondary` / `--text-muted`),强调色为浅蓝(`--primary` 系列);
- **背景与遮罩**:背景素材为可替换内容,用户可不定期更换,其具体文件、类型与画面不作为设计规范;背景层的显示方式由 CSS 定义(居中、覆盖、固定附着),其上由 `body::before` 叠加全屏深色渐变遮罩,保证文字对比度;
- **玻璃表面 token**:侧边栏、搜索框、卡片分别使用 `--surface-sidebar` / `--surface-search` / `--surface-card`(及 `--surface-card-hover`)半透明渐变,配 `--border-glass` 描边、`--shadow-*` 阴影与 `backdrop-filter: blur()` 毛玻璃效果;圆角与栏宽由 `--radius-*` / `--sidebar-*` token 控制;
- **桌面布局**:左侧固定玻璃侧边栏(展开 240px / 折叠 72px,`localStorage` 持久化),内容区随侧边栏宽度偏移;首页为纵向排列、水平居中的时钟、搜索框、常用链接;分类页仅文字元素挂玻璃效果,保持背景清晰;分类页网格默认 2 列,≥1280px 时 3 列;
- **移动端适配(≤767px)**:侧边栏转为固定顶部横栏(品牌行 + 横向滚动的分类导航),折叠按钮隐藏,首页与分类页顶部留白相应加大;≤420px 时常用链接降为单列、快捷键提示隐藏;分类页网格在 ≤999px 时降为 1 列;低视口高度时分类页从固定高度回退为整页滚动;全局支持 `prefers-reduced-motion`。
---
## 6. 数据结构
### 6.1 分类数据
```ts
{
id: "dev",
title: "开发工具",
description: "高效的开发工具和资源集合,提升你的开发体验和效率。",
icon: "code",
sort: 20
}
```
字段说明:
```text
id: 分类唯一标识
title: 分类名称
description: 分类描述(分类页标题下方展示)
icon: 图标名称(侧边栏,lucide 图标名)
sort: 排序值
```
当前数据共 8 个分类:AI 工具、开发工具、设计资源、学习资料、效率工具、娱乐媒体、游戏资料、生活服务。
### 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: 分类内排序值
```
当前数据共 51 条链接,各分类均有内容。首页常用链接 6 条(按 `pinnedSort`):GitHub、ChatGPT、Gmail、Notion、YouTube、Bilibili;`getPinnedLinks` 默认上限 8 条,取 `min(pinned 数量, 8)`。
### 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
}
}
```
> **注意**:`theme` 与 `background` 目前仅作为数据模型与校验存在(`services/settings.ts` 解析校验,`theme` 当前仅接受 `"light-glass"`),页面未消费这两个字段,**不驱动样式**。界面视觉为固定深色玻璃,见 5.5。页面实际消费的字段是 `siteName`、`search.*` 与 `sidebar.defaultCollapsed`。
### 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` / `sparkles`),不参与此 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,6 个)
分类页路由 + 卡片网格 + 空状态组件
桌面端响应式
```
数据范围:8 个分类共 51 条链接,全部分类均有内容;空状态组件保留为数据缺失时的兜底展示。首页常用链接 6 条:GitHub、ChatGPT、Gmail、Notion、YouTube、Bilibili。
### 第二阶段:数据补全与视觉定型(已完成)
```text
补全全部分类的链接数据 → 已完成(8 个分类均有内容)
深色玻璃主题 → 已完成(固定深色玻璃,见 5.5)
移动端 / 平板适配 → 移动端已完成(顶部横栏,见 5.5);
平板沿用断点降级,未做专项设计
```
### 第三阶段:数据访问层抽象(已完成)
```text
封装 links / categories / settings service → 已实现(src/services/)
统一 TypeScript 类型 → 已实现(src/types/)
统一错误处理与 loading 状态 → 数据解析校验失败即抛错;
loading 状态针对远程数据,当前本地数据不涉及
```
此阶段完成后,页面组件不直接关心数据来自本地配置还是 API——当前已达成:页面只依赖 service 接口。
### 第四阶段:Go 后端 API(未来)
```text
Go API 服务
数据库连接(PostgreSQL)
links / categories / settings 查询接口
基础 CORS 配置
Docker 部署预留
```
### 第五阶段:管理能力(未来)
```text
新增 / 编辑 / 删除链接
调整排序、修改分类
修改背景、主题
导入导出配置
```
管理能力不进入首页主视觉,放到独立管理页或隐藏入口。
---
## 10. 项目目录结构
实际目录按 feature-first 组织:
```text
polaris/
public/
backgrounds/ # 背景素材(可随时替换,不作为设计基线)
brand/ # 站点 Logo
icons/ # 自定义 SVG 图标(icon: "local:xxx" 引用)
src/
app/
layout.tsx # 根布局、元信息、viewport(深色)
page.tsx # 首页(server 组件,经 service 取数)
categories/[categoryId]/page.tsx # 分类页(generateStaticParams,未知分类 404)
globals.css # Tailwind + 深色玻璃设计 token 与组件样式(见 5.5)
features/ # 按功能域组织
home/ # 首页
components/ # home-content / clock-greeting / search-box /
# search-provider-menu / local-search-results / quick-links
hooks/ # use-clock
lib/ # date-time / search-providers / provider-preference /
# local-search(纯函数 + 测试)
category/ # 分类页
components/ # category-content / category-header /
# category-section-header / tool-grid / tool-card /
# category-empty-state
navigation/ # 外壳与侧边栏
components/ # app-shell / sidebar / category-icon
lib/ # sidebar-preference
components/
shared/ # site-icon(四级 fallback)/ icon-candidates /
# simple-icon-map / glass-panel
ui/ # button / dropdown-menu / tooltip(shadcn/ui)
data/ # links.json / categories.json / settings.json
# + data.test.ts(结构校验)
services/ # links.ts / categories.ts / settings.ts + services.test.ts
types/ # navigation.ts / settings.ts / search.ts
lib/ # utils.ts(cn)
next.config.ts # output: 'standalone'
vitest.config.ts
```
---
## 11. 最终目标
> 打开 Polaris 后,用户可以立刻搜索、立刻访问常用网站,也可以通过侧边栏进入分类页面。页面具有个人氛围,但保持干净、高效、克制。当前数据内置于本地 JSON 配置,页面统一经 service 层取数。