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

20 KiB
Raw Blame History

Polaris 项目说明文档

关联文档: Polaris 首页设计文档 —— 首页布局、搜索交互、视觉规范 Polaris 分类页设计文档 —— 分类页布局、卡片、状态设计

配套资产:站点 Logo、Polaris 页面设计图

本文以当前代码实现为准(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 前端技术栈

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 输出:

// 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 层已实现,页面与数据文件解耦:

// 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,未实施)。

候选组合:

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 分类数据

{
  id: "dev",
  title: "开发工具",
  description: "高效的开发工具和资源集合,提升你的开发体验和效率。",
  icon: "code",
  sort: 20
}

字段说明:

id:          分类唯一标识
title:       分类名称
description: 分类描述(分类页标题下方展示)
icon:        图标名称(侧边栏,lucide 图标名)
sort:        排序值

当前数据共 8 个分类:AI 工具、开发工具、设计资源、学习资料、效率工具、娱乐媒体、游戏资料、生活服务。

6.2 链接数据

{
  id: "github",
  title: "GitHub",
  description: "全球最大的代码托管平台",
  url: "https://github.com",
  icon: "si:github",
  categoryId: "dev",
  pinned: true,
  pinnedSort: 10,
  keywords: ["代码", "仓库", "开源", "git"],
  sort: 10
}

字段说明:

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 设置数据

{
  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:

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:

GET /api/categories
GET /api/links
GET /api/links?categoryId=dev
GET /api/settings

后续管理能力:

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

背景相关:

POST /api/backgrounds
GET  /api/backgrounds
PUT  /api/settings/background

8. 数据库表设计草案(未来)

草案使用跨数据库通用语法;JSON 以 TEXT 存储。PostgreSQL 下可将 TEXT(存 JSON)字段替换为 JSONB 以获得索引与查询能力。

8.1 categories

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
);
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

CREATE TABLE settings (
  key TEXT PRIMARY KEY,
  value TEXT NOT NULL,                  -- JSON 字符串
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

9. 开发阶段

第一至第三阶段已实现(以当前代码为准),第四、五阶段为未来规划。

第一阶段:前端基础版(已完成)

目标:完成首页与分类页基础体验。

背景层与遮罩(样式由 CSS 定义,素材可替换)
左侧边栏 + 折叠(localStorage 持久化)+ 导航选中态
时间 / 日期 / 问候语
搜索框(Google / Bing / GitHub / 本地搜索)+ Ctrl+K 快捷键
常用链接(pinned,6 个)
分类页路由 + 卡片网格 + 空状态组件
桌面端响应式

数据范围:8 个分类共 51 条链接,全部分类均有内容;空状态组件保留为数据缺失时的兜底展示。首页常用链接 6 条:GitHub、ChatGPT、Gmail、Notion、YouTube、Bilibili。

第二阶段:数据补全与视觉定型(已完成)

补全全部分类的链接数据                 → 已完成(8 个分类均有内容)
深色玻璃主题                           → 已完成(固定深色玻璃,见 5.5)
移动端 / 平板适配                      → 移动端已完成(顶部横栏,见 5.5);
                                         平板沿用断点降级,未做专项设计

第三阶段:数据访问层抽象(已完成)

封装 links / categories / settings service  → 已实现(src/services/)
统一 TypeScript 类型                        → 已实现(src/types/)
统一错误处理与 loading 状态                 → 数据解析校验失败即抛错;
                                             loading 状态针对远程数据,当前本地数据不涉及

此阶段完成后,页面组件不直接关心数据来自本地配置还是 API——当前已达成:页面只依赖 service 接口。

第四阶段:Go 后端 API(未来)

Go API 服务
数据库连接(PostgreSQL)
links / categories / settings 查询接口
基础 CORS 配置
Docker 部署预留

第五阶段:管理能力(未来)

新增 / 编辑 / 删除链接
调整排序、修改分类
修改背景、主题
导入导出配置

管理能力不进入首页主视觉,放到独立管理页或隐藏入口。


10. 项目目录结构

实际目录按 feature-first 组织:

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 层取数。