Compare commits
2
Commits
fec2a97936
...
86f6eb39b2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86f6eb39b2 | ||
|
|
d53dbac88e |
@@ -2,11 +2,8 @@ name: Polaris CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ts/web/nextjs/polaris/**
|
||||
- .gitea/workflows/polaris-ci.yml
|
||||
tags:
|
||||
- 'polaris-v*'
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
@@ -17,31 +14,31 @@ jobs:
|
||||
run:
|
||||
working-directory: ts/web/nextjs/polaris
|
||||
steps:
|
||||
- name: Check out repository
|
||||
- name: 检出仓库
|
||||
uses: https://gitea.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
|
||||
- name: Install dependencies
|
||||
- name: 安装依赖
|
||||
run: corepack pnpm install --frozen-lockfile
|
||||
|
||||
- name: Lint
|
||||
- name: 代码检查
|
||||
run: corepack pnpm lint
|
||||
|
||||
- name: Typecheck
|
||||
- name: 类型检查
|
||||
run: corepack pnpm typecheck
|
||||
|
||||
- name: Test
|
||||
- name: 运行测试
|
||||
run: corepack pnpm test
|
||||
|
||||
- name: Build standalone server
|
||||
- name: 构建独立服务器
|
||||
run: corepack pnpm build
|
||||
|
||||
- name: Prepare standalone output
|
||||
- name: 准备独立部署文件
|
||||
run: |
|
||||
test -f .next/standalone/server.js
|
||||
cp -r public .next/standalone/
|
||||
cp -r .next/static .next/standalone/.next/
|
||||
|
||||
- name: Smoke test standalone server
|
||||
- name: 独立服务器冒烟测试
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -56,32 +53,32 @@ jobs:
|
||||
ready=1
|
||||
break
|
||||
fi
|
||||
kill -0 "$server_pid" 2>/dev/null || { echo "server process exited early" >&2; exit 1; }
|
||||
kill -0 "$server_pid" 2>/dev/null || { echo "服务器进程提前退出" >&2; exit 1; }
|
||||
sleep 1
|
||||
done
|
||||
test "$ready" -eq 1 || { echo "server not ready within 30s" >&2; exit 1; }
|
||||
test "$ready" -eq 1 || { echo "服务器在 30 秒内未就绪" >&2; exit 1; }
|
||||
|
||||
curl -fsS -o /dev/null "$base_url/categories/dev/"
|
||||
curl -fsS -o /dev/null "$base_url/brand/logo.svg"
|
||||
|
||||
static_asset=$(curl -fsS "$base_url/" | grep -o '/_next/static/[^"]*' | sed -n '1p')
|
||||
test -n "$static_asset" || { echo "homepage references no /_next/static asset" >&2; exit 1; }
|
||||
test -n "$static_asset" || { echo "首页未引用 /_next/static 静态资源" >&2; exit 1; }
|
||||
curl -fsS -o /dev/null "$base_url$static_asset"
|
||||
|
||||
publish:
|
||||
needs: verify
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/polaris-v')
|
||||
runs-on: polaris-ci
|
||||
container: node:22.23.2-bookworm
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Check out repository
|
||||
- name: 检出仓库
|
||||
uses: https://gitea.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
|
||||
# Daemon comes from the runner (gitea/runner:3-dind-rootless) via the
|
||||
# job container's /var/run/docker.sock. Only the CLI is installed here;
|
||||
# DOCKER_HOST stays unset and no host socket is mounted.
|
||||
- name: Install Docker CLI
|
||||
- name: 安装 Docker 命令行工具
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
@@ -89,25 +86,25 @@ jobs:
|
||||
apt-get install -y --no-install-recommends docker.io
|
||||
docker version
|
||||
|
||||
- name: Resolve image reference
|
||||
- name: 确定镜像引用
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
repository="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is not set}"
|
||||
repository="${GITHUB_REPOSITORY:?未设置 GITHUB_REPOSITORY}"
|
||||
owner="${repository%%/*}"
|
||||
owner="${owner,,}"
|
||||
if ! [[ "$owner" =~ ^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$ ]]; then
|
||||
echo "::error::owner segment '$owner' is not a valid registry path component" >&2
|
||||
echo "::error::所有者名称 '$owner' 不是有效的镜像仓库路径组成部分" >&2
|
||||
exit 1
|
||||
fi
|
||||
image="git.labmem.icu/${owner}/polaris:${GITHUB_SHA}"
|
||||
echo "image reference: $image"
|
||||
echo "镜像引用:$image"
|
||||
echo "IMAGE=$image" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build image
|
||||
- name: 构建镜像
|
||||
run: docker build -t "$IMAGE" ts/web/nextjs/polaris
|
||||
|
||||
- name: Smoke test image
|
||||
- name: 镜像冒烟测试
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -128,19 +125,19 @@ jobs:
|
||||
break
|
||||
fi
|
||||
if [[ "$(docker inspect -f '{{.State.Running}}' "$container")" != "true" ]]; then
|
||||
echo "container exited early, logs:" >&2
|
||||
echo "容器提前退出,日志:" >&2
|
||||
docker logs "$container" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
test "$ready" -eq 1 || { echo "server not ready within 30s" >&2; docker logs "$container" >&2 || true; exit 1; }
|
||||
test "$ready" -eq 1 || { echo "服务器在 30 秒内未就绪" >&2; docker logs "$container" >&2 || true; exit 1; }
|
||||
|
||||
probe /categories/dev/ || { echo "smoke test failed: /categories/dev/" >&2; exit 1; }
|
||||
probe /brand/logo.svg || { echo "smoke test failed: /brand/logo.svg" >&2; exit 1; }
|
||||
echo "smoke test passed for $IMAGE"
|
||||
probe /categories/dev/ || { echo "冒烟测试失败:/categories/dev/" >&2; exit 1; }
|
||||
probe /brand/logo.svg || { echo "冒烟测试失败:/brand/logo.svg" >&2; exit 1; }
|
||||
echo "镜像 $IMAGE 冒烟测试通过"
|
||||
|
||||
- name: Log in and push image
|
||||
- name: 登录并推送镜像
|
||||
shell: bash
|
||||
env:
|
||||
POLARIS_REGISTRY_USER: ${{ secrets.POLARIS_REGISTRY_USER }}
|
||||
@@ -160,7 +157,7 @@ jobs:
|
||||
|
||||
printf '%s' "$POLARIS_REGISTRY_TOKEN" | docker --config "$docker_config_dir" login git.labmem.icu --username "$POLARIS_REGISTRY_USER" --password-stdin >/dev/null
|
||||
|
||||
echo "pushing $IMAGE"
|
||||
echo "正在推送 $IMAGE"
|
||||
# tee keeps push output visible in the log; pipefail (already set
|
||||
# via `set -euo pipefail`) fails the step when push itself fails.
|
||||
docker --config "$docker_config_dir" push "$IMAGE" 2>&1 | tee "$push_log_file"
|
||||
@@ -171,6 +168,6 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "published image: $IMAGE"
|
||||
echo "digest: $digest"
|
||||
echo "manual deploy reference: docker pull ${IMAGE%%:*}@$digest"
|
||||
echo "已发布镜像:$IMAGE"
|
||||
echo "镜像摘要:$digest"
|
||||
echo "手动部署引用:docker pull ${IMAGE%%:*}@$digest"
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
name: Polaris runner smoke test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- .gitea/workflows/polaris-runner-smoke.yml
|
||||
|
||||
jobs:
|
||||
smoke:
|
||||
runs-on: polaris-ci
|
||||
steps:
|
||||
- name: Confirm runner can execute a job
|
||||
run: |
|
||||
echo "Polaris runner is working"
|
||||
uname -m
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
本目录提供在服务器上以**独立 Compose 项目**运行 Polaris 的最小配置。
|
||||
|
||||
第一阶段流程:CI(Gitea Actions)在校验(lint / typecheck / test / build / standalone
|
||||
冒烟)通过后,**自动构建并推送**镜像 `git.labmem.icu/<repo-owner>/polaris:<full-commit-sha>`
|
||||
到 Gitea registry;宿主机(服务器)首次部署时用**只读**凭据 `docker login`,在 `.env`
|
||||
中固定该镜像的 digest,再 `pull + up` 单服务运行。
|
||||
第一阶段流程:仅当推送 `polaris-v*` 标签时才运行 CI(Gitea Actions)整条验证+发布
|
||||
工作流——`verify` job 通过校验(lint / typecheck / test / build / standalone 冒烟)后,
|
||||
`publish` job 构建镜像 `git.labmem.icu/<repo-owner>/polaris:<full-commit-sha>`(完整
|
||||
commit SHA 作 tag)并**自动推送**到 Gitea registry,从 push 日志解析输出 digest;
|
||||
分支/路径推送不触发 CI。宿主机(服务器)首次部署时用**只读**凭据 `docker login`,
|
||||
在 `.env` 中以 digest 固定该镜像,再 `pull + up` 单服务运行。
|
||||
|
||||
## 文件说明
|
||||
|
||||
@@ -16,8 +18,9 @@
|
||||
|
||||
设计约束:
|
||||
|
||||
- 镜像来源:CI 按 commit sha tag 推送;部署侧用 `image: ${POLARIS_IMAGE:?...}`
|
||||
必填 + `sha256:<digest>` 固定,杜绝 latest 漂移
|
||||
- 镜像来源:CI 仅在 `polaris-v*` 标签触发的发布中,以完整 commit SHA 为 tag 推送;
|
||||
部署侧约定在 `.env` 写 `...@sha256:<digest>` 固定镜像,杜绝 latest 漂移;
|
||||
`compose.yaml` 的 `image: ${POLARIS_IMAGE:?...}` 只校验变量非空,不校验 digest 格式
|
||||
- 只加入外部网络 `net-public`(与 nginx 同网);**不连** `net-db`
|
||||
- 不映射宿主机端口(无 `ports:`),仅通过 nginx 反代访问
|
||||
- 无 DB/Redis、无 secrets、无源码/数据卷挂载
|
||||
@@ -41,8 +44,9 @@
|
||||
|
||||
## 待现场验证(不要假设,逐项落实)
|
||||
|
||||
- workflow 中 build/push 步骤的实际行为,以及 **digest 在 CI 运行日志中的输出形式**
|
||||
(workflow 正在另行更新,本 lane 不改它;下文涉及处均以实际运行日志为准)
|
||||
- workflow 的 build/push 与 digest 输出逻辑已定稿:推送步骤从 push 日志解析
|
||||
`digest: sha256:<64 位十六进制>`,回显「镜像摘要」与手动部署引用,解析失败即 job 失败;
|
||||
**待现场验证**的是真实 Gitea registry / runner 下 push 输出能否匹配该解析
|
||||
- registry / runner 实际行为:登录、推送、按 digest 拉取
|
||||
- 生产 Nginx 行为(见「Nginx 反代」)
|
||||
- 服务器上 `net-public` 网络名、既有 Compose 项目与目录布局
|
||||
@@ -51,16 +55,19 @@
|
||||
|
||||
## 首次发布步骤
|
||||
|
||||
### 1) 触发并等待 CI,记录镜像与 digest
|
||||
### 1) 推送标签触发 CI,记录镜像与 digest
|
||||
|
||||
```bash
|
||||
# 推送到触发分支/路径后,在 Gitea 网页打开 Actions 运行页,等待 job 全绿
|
||||
# 推送 polaris-v* 标签触发整条工作流(分支/路径推送不触发 CI),例如:
|
||||
git tag polaris-v0.1.0 && git push origin polaris-v0.1.0
|
||||
# 在 Gitea 网页打开 Actions 运行页,等待 verify 与 publish job 全绿
|
||||
```
|
||||
|
||||
- 确认校验/构建/冒烟全部通过后,从该次运行日志的**推送步骤**记录:
|
||||
- 确认全部 job 通过后,从该次运行日志的**推送步骤**记录:
|
||||
- 镜像:`git.labmem.icu/<repo-owner>/polaris:<full-commit-sha>`
|
||||
- digest:`sha256:<digest>`(日志中的具体展示形式**待现场验证**;
|
||||
若日志未直接给出,用下文「如何确认真实 digest」的方法在部署前核对)
|
||||
- digest:推送步骤解析成功时直接回显「镜像摘要:sha256:<digest>」与
|
||||
`docker pull ...@sha256:<digest>` 手动部署引用;实际输出以日志为准,
|
||||
若未给出,用下文「如何确认真实 digest」的方法在部署前核对
|
||||
- 记下对应 commit sha 与 run 编号,便于追溯。
|
||||
|
||||
### 2) 拷贝示例到服务器,并确认现状与项目路径
|
||||
@@ -142,8 +149,9 @@ docker exec <nginx容器名> wget -qO- http://polaris:3000/ >/dev/null && echo o
|
||||
|
||||
## 升级与回退
|
||||
|
||||
升级(**无零停机承诺**):新 commit 的 CI 运行**全绿**并取得新 digest 后,
|
||||
把新 digest 写入 `.env` 的 `POLARIS_IMAGE`,旧值保留为注释行,然后:
|
||||
升级(**无零停机承诺**):为要发布的新 commit 打新的 `polaris-v*` 标签触发 CI,
|
||||
运行**全绿**并取得新 digest 后,把新 digest 写入 `.env` 的 `POLARIS_IMAGE`,
|
||||
旧值保留为注释行,然后:
|
||||
|
||||
```bash
|
||||
docker compose pull polaris && docker compose up -d polaris
|
||||
@@ -162,7 +170,8 @@ docker compose pull polaris && docker compose up -d polaris
|
||||
|
||||
按可靠程度排序,最终以「按 digest 拉取成功」为准:
|
||||
|
||||
- **CI 运行日志**:推送步骤的输出通常包含 digest;具体展示形式取决于 workflow 写法,
|
||||
- **CI 运行日志**:workflow 已实现 digest 输出——推送步骤解析 push 日志后回显
|
||||
「镜像摘要」与 `docker pull` 手动部署引用;真实 registry / runner 下输出是否符合预期
|
||||
**待现场验证**,以实际日志为准。
|
||||
- **部署前交叉核对**(任一已登录该 registry、装有 buildx 的机器):
|
||||
`docker buildx imagetools inspect git.labmem.icu/<repo-owner>/polaris:<full-commit-sha>`
|
||||
@@ -173,8 +182,8 @@ docker compose pull polaris && docker compose up -d polaris
|
||||
- 已拉取过 tag 的机器可查本地记录的远端摘要:
|
||||
`docker image inspect --format '{{index .RepoDigests 0}}' git.labmem.icu/<repo-owner>/polaris:<full-commit-sha>`。
|
||||
- 注意:`.env` 固定的是 manifest(列表)digest;多架构镜像按 digest 拉取时由运行时
|
||||
选择本平台对应的 manifest。CI 采用单平台还是多平台构建取决于 workflow
|
||||
(**待现场验证**),构建平台需覆盖服务器架构。
|
||||
选择本平台对应的 manifest。workflow 用单条 `docker build` 构建单平台镜像
|
||||
(平台取决于 runner 架构),是否覆盖服务器架构**待现场验证**。
|
||||
- commit sha tag 正常不应被重复推送;若发现同名 tag 的 digest 变化,
|
||||
先排查 CI 再部署,不要直接沿用旧记录。
|
||||
|
||||
|
||||
@@ -5,6 +5,17 @@
|
||||
> 状态:用户已批准,可进入实施
|
||||
>
|
||||
> 实现分支:`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. 背景与目标
|
||||
|
||||
|
||||
@@ -4,6 +4,17 @@
|
||||
> [Polaris 项目说明文档](./Polaris%20项目说明文档.md) —— 项目定位、技术选型、数据结构、开发阶段
|
||||
> [Polaris 首页设计文档](./Polaris%20首页设计文档.md) —— 首页布局与搜索交互
|
||||
|
||||
## 0. 当前实现状态(2026-10-14 校对)
|
||||
|
||||
本章为现状说明。第 1–23 章保留立项时的历史设计意图;其中浅色玻璃样式、白色大面板容器和数据示例均为历史方案,视觉细节以本章与 `src/app/globals.css` 为准。
|
||||
|
||||
- **路由**:`/categories/[categoryId]` 已实现,8 个分类全部静态预生成,未知分类返回 404。
|
||||
- **布局**:左侧边栏与首页一致(桌面固定悬浮;≤767px 变为顶部横栏,折叠按钮隐藏、导航横向滚动)。主内容区**不再是一整块白色玻璃面板**——面板容器透明,使背景素材保持清晰,玻璃只压在承载文字的元素上:标题与描述带轻边框和文字投影,「全部工具」列表放在一层薄纱视口(rgba(5, 14, 29, 0.18)、圆角 20px)内,工具卡片为深色玻璃。
|
||||
- **滚动**:桌面端页面高度固定为 `100svh`,工具列表在内部视口滚动(`overscroll-behavior: contain`、可聚焦);视口过矮(`max-height` 断点)或移动端回退为整页滚动。
|
||||
- **网格现状**:工具网格基础为两列;≥1280px 升为三列,≤999px 降为单列,即 1000–1279px 保持两列。手机顶部横栏与单列已完成;平板/中等窗口的两列已实现,与历史计划(768px–1279px 两列)的差异仅剩 768px–999px 区间当前同为单列。
|
||||
- **视觉**:深色玻璃主题,tokens 与首页共用(见[首页设计文档](./Polaris%20首页设计文档.md)第 0 章);背景素材可整体替换,背景层显示与遮罩样式由现有 CSS 管理,素材类型/路径/画面不作为设计规范。
|
||||
- **数据现状**:全站 8 个分类共 51 条链接;本文以「开发工具」为例的数据(15 条)为立项期示意,当前该分类实际 12 条。
|
||||
|
||||
## 1. 页面定位
|
||||
|
||||
Polaris 的分类页用于展示某一类导航资源,例如:
|
||||
@@ -72,6 +83,8 @@ Figma
|
||||
|
||||
其他分类页面复用相同结构,只需要替换分类名称、描述和链接数据。
|
||||
|
||||
> 2026-10-14 说明:本节与第 12 节的链接清单、数量均为立项期示意数据,不代表当前 `src/data/` 内容。
|
||||
|
||||
---
|
||||
|
||||
## 3. 与首页的关系
|
||||
@@ -198,6 +211,8 @@ background: rgba(99, 102, 241, 0.1);
|
||||
color: #2563eb;
|
||||
```
|
||||
|
||||
> 2026-10-14 实现状态:上方浅蓝紫数值为历史方案。当前选中态为 `--primary-soft`(rgba(147, 190, 255, 0.15))底色 + `--primary-hover` 文字色 + 一圈淡色内描边,圆角 16px 不变。
|
||||
|
||||
图标和文字同时高亮。
|
||||
|
||||
选中项圆角:
|
||||
@@ -279,6 +294,8 @@ border-radius: 24px;
|
||||
box-shadow: 0 24px 80px rgba(15, 23, 42, 0.1);
|
||||
```
|
||||
|
||||
> 2026-10-14 实现状态:上方白色玻璃面板为历史方案。当前面板容器已透明化(见第 0 章):`GlassPanel` 结构保留,但边框、背景、阴影与 backdrop-filter 均被覆盖为透明或无,背景素材直接可见。
|
||||
|
||||
容器与页面边距:
|
||||
|
||||
```css
|
||||
@@ -482,7 +499,7 @@ grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
```
|
||||
|
||||
> 移动端单列布局为后续阶段工作,第一版仅适配桌面端与桌面窗口缩窄场景。
|
||||
> 历史说明:立项时移动端单列为后续阶段工作。当前状态(2026-10-14):两列为基础布局,≥1280px 升为三列,≤999px(含手机及 768px–999px 窗口)降为单列;历史计划的「768px–1279px 两列」已基本实现,仅 768px–999px 区间为单列。
|
||||
|
||||
---
|
||||
|
||||
@@ -573,6 +590,8 @@ border-radius: 18px;
|
||||
box-shadow: 0 14px 40px rgba(15, 23, 42, 0.08);
|
||||
```
|
||||
|
||||
> 2026-10-14 实现状态:上方浅色样式为历史方案。当前工具卡为深色玻璃 rgba(12, 24, 42, 0.65) + blur(10px),高 82px、圆角 18px,hover 提亮为 rgba(25, 43, 65, 0.84) 并轻微上浮。
|
||||
|
||||
Hover 状态:
|
||||
|
||||
```css
|
||||
@@ -678,6 +697,8 @@ JavaScript 包管理器
|
||||
|
||||
## 12. 开发工具页数据示例
|
||||
|
||||
> 2026-10-14 说明:本节数据为立项期示例(15 条),不代表当前 `src/data/`;当前全站 8 类 51 条链接,开发工具分类 12 条。字段结构与现行 `NavLink` 类型一致,仍可作为格式参考。
|
||||
|
||||
### 12.1 分类数据
|
||||
|
||||
```json
|
||||
@@ -955,11 +976,15 @@ Hover 时只做轻微反馈:
|
||||
768px - 1279px:两列
|
||||
```
|
||||
|
||||
当前实现(2026-10-14):两列为基础布局,≥1280px 三列、≤999px 单列;历史计划的「768px–1279px 两列」已基本实现,仅其中 768px–999px 区间当前为单列(见第 0 章)。
|
||||
|
||||
---
|
||||
|
||||
### 14.2 平板与移动端(后续阶段)
|
||||
### 14.2 平板与移动端
|
||||
|
||||
第一版仅适配桌面端。平板与移动端适配为后续阶段工作,届时参考以下方向:
|
||||
> 2026-10-14 实现状态:移动端适配已完成——≤767px 时侧边栏变为**常驻顶部横栏**(Logo + 横向滚动导航,非抽屉),工具卡片单列,短屏与移动端回退整页滚动;平板方向的「边栏宽度略小(220px)」与「工具卡片两列」已实现(两列覆盖 1000px–1279px,768px–999px 区间为单列),下列历史方向中的「抽屉式边栏」未采用。
|
||||
|
||||
立项时(仅适配桌面端)的参考方向:
|
||||
|
||||
```text
|
||||
平板端:边栏宽度略小(200px - 220px),工具卡片两列
|
||||
@@ -1201,6 +1226,8 @@ type CategoryPageData = {
|
||||
侧边栏移动端适配
|
||||
```
|
||||
|
||||
> 2026-10-14 实现状态:「平板两列」(1000px–1279px 区间,768px–999px 除外)、「移动端一列」与「侧边栏移动端适配」已完成(见第 0 章)。
|
||||
|
||||
---
|
||||
|
||||
### 第三阶段
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
> [Polaris 首页设计文档](./Polaris%20首页设计文档.md) —— 首页布局、搜索交互、视觉规范
|
||||
> [Polaris 分类页设计文档](./Polaris%20分类页设计文档.md) —— 分类页布局、卡片、状态设计
|
||||
>
|
||||
> 配套资产:[占位 Logo](../../public/brand/logo.svg)(后续替换)、[Polaris 页面设计图](../assets/Polaris页面设计图.png)
|
||||
> 配套资产:[站点 Logo](../../public/brand/logo.svg)、[Polaris 页面设计图](../assets/Polaris页面设计图.png)
|
||||
|
||||
> 本文以当前代码实现为准(2026-10-14 对齐)。早期决策中已被后续实现取代的条目在第 4 节单独标注为「历史」,不再代表现状。
|
||||
|
||||
## 1. 项目名称
|
||||
|
||||
@@ -20,15 +22,15 @@ Polaris 原意为“北极星”,具有方向、导航、定位、指引的含
|
||||
|
||||
Polaris 不是后台管理系统,也不是信息密集型 Dashboard。
|
||||
|
||||
> 一个具有个人审美、可自定义背景、可快速搜索和访问常用链接的浏览器起始页。
|
||||
> 一个具有个人审美、可快速搜索和访问常用链接的浏览器起始页。
|
||||
|
||||
产品形态是**动态可配置的个人导航站**:第一版可以静态部署,数据写死在本地配置中,但页面具有动态交互(实时时钟、搜索、侧边栏折叠、快捷键等),后续将本地数据源替换为 Go 后端 API,不需要推翻前端整体结构。
|
||||
产品形态是**个人导航站**:数据内置于本地 JSON 配置、随构建打包,页面具有动态交互(实时时钟、搜索、侧边栏折叠、快捷键等)。页面统一经 service 层取数,不依赖具体数据源形态;后续数据源迁移另行规划。
|
||||
|
||||
---
|
||||
|
||||
## 3. 项目目标
|
||||
|
||||
第一阶段目标:
|
||||
第一阶段目标(已全部实现):
|
||||
|
||||
1. 提供一个美观的个人导航首页;
|
||||
2. 支持快速访问常用链接;
|
||||
@@ -36,42 +38,49 @@ Polaris 不是后台管理系统,也不是信息密集型 Dashboard。
|
||||
4. 支持搜索框(Google / Bing / GitHub / 本地);
|
||||
5. 支持当前时间与日期展示;
|
||||
6. 支持左侧边栏折叠(状态持久化);
|
||||
7. 支持背景自定义的设计预留;
|
||||
8. 数据第一版写死在本地 JSON 数据配置中;
|
||||
9. 代码结构预留未来接入 Go 后端接口的能力。
|
||||
7. 数据内置于本地 JSON 配置,结构由 Vitest 校验测试兜底;
|
||||
8. 页面组件统一经 service 层取数。
|
||||
|
||||
后续阶段目标:
|
||||
背景自定义目前停留在数据模型与校验预留(`settings.background`),不驱动样式,见 6.3。
|
||||
|
||||
后续阶段目标(未实现):
|
||||
|
||||
1. 从 Go 后端 API 获取链接、分类、设置等数据;
|
||||
2. 使用数据库持久化配置;
|
||||
3. 支持后台或隐藏入口管理链接;
|
||||
4. 支持用户自定义背景;
|
||||
5. 支持主题色、深色模式、导入导出、移动端适配等个性化能力。
|
||||
4. 让 `settings.theme` / `settings.background` 真正驱动界面,支持自定义背景与主题切换;
|
||||
5. 支持导入导出等个性化能力。
|
||||
|
||||
---
|
||||
|
||||
## 4. 已确认的关键决策
|
||||
|
||||
以下决策已于 2026-08-21 确认,作为实施基线:
|
||||
以下决策于 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 模式显示本地结果下拉 |
|
||||
| # | 决策项 | 结论 | 状态 |
|
||||
| --- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ---- |
|
||||
| 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 模式显示本地结果下拉 | 现行 |
|
||||
|
||||
2026-10-12 部署阶段将第 2 项由静态导出调整为 standalone 服务端输出;当前实现见第 5.2 节。初期内容仍在构建时从本地 JSON 生成,在线数据管理需后续接入后端。
|
||||
历史决策的现行实现:
|
||||
|
||||
- **#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。
|
||||
|
||||
---
|
||||
|
||||
@@ -90,14 +99,14 @@ simple-icons
|
||||
|
||||
说明:
|
||||
|
||||
1. **Next.js**:适合从静态页面起步,后续接入动态接口;
|
||||
1. **Next.js**:以本地数据起步,后续接入动态接口;
|
||||
2. **TypeScript**:适合维护链接、分类、配置等结构化数据;
|
||||
3. **Tailwind CSS**:适合快速实现玻璃感、响应式、主题变量;
|
||||
4. **shadcn/ui**:用于基础组件,例如 Button、Input、Dialog、Command;
|
||||
3. **Tailwind CSS**:配合 CSS 变量实现玻璃表面、响应式与视觉 token;
|
||||
4. **shadcn/ui**:用于基础组件(Button、DropdownMenu、Tooltip);
|
||||
5. **lucide-react**:用于侧边栏和操作图标;
|
||||
6. **simple-icons**:品牌图标库(npm 包,按 slug 引用)。
|
||||
|
||||
测试:Vitest + Testing Library,重点覆盖纯函数(问候语分段、本地搜索匹配排序、icon 解析、搜索 URL 模板)与核心交互组件。
|
||||
测试:Vitest + Testing Library,覆盖静态数据结构校验、service 层行为、纯函数(时间格式化与问候语分段、本地搜索匹配排序、icon 候选解析、搜索 URL 模板、偏好读写)与核心交互组件。
|
||||
|
||||
### 5.2 渲染策略
|
||||
|
||||
@@ -105,47 +114,46 @@ simple-icons
|
||||
|
||||
```ts
|
||||
// next.config.ts
|
||||
const nextConfig = { output: "standalone" }
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
trailingSlash: true,
|
||||
images: { unoptimized: true },
|
||||
}
|
||||
```
|
||||
|
||||
- 首页与全部分类页在构建时从本地数据生成(`generateStaticParams`);
|
||||
- 首页与全部分类页为 server 组件,构建时经 service 层从本地数据生成;分类页通过 `generateStaticParams` 预生成,`dynamicParams = false`(未知分类统一 404);
|
||||
- 时钟、搜索、侧边栏折叠等交互以 client 组件实现;
|
||||
- 构建产物为 `.next/standalone` 下的最小 Node 服务器(`server.js`),`public` 与 `.next/static` 需按 Next 文档在部署时手动复制;
|
||||
- 后续接 Go 后端时,改造 service 层为 fetch,数据页面改为客户端获取。
|
||||
- 构建产物为 `.next/standalone` 下的最小 Node 服务器(`server.js`),`public` 与 `.next/static` 需按 Next 文档在部署时手动复制。
|
||||
|
||||
### 5.3 数据访问层
|
||||
### 5.3 数据访问层(已实现)
|
||||
|
||||
**页面组件不直接依赖数据文件,一律通过 service 层获取数据。**
|
||||
|
||||
第一版:
|
||||
当前 service 层已实现,页面与数据文件解耦:
|
||||
|
||||
```ts
|
||||
import links from "@/data/links.json"
|
||||
// src/services/links.ts
|
||||
export function getLinks(): NavLink[]
|
||||
export function getLinksByCategory(categoryId: string): NavLink[] // 按 sort 排序
|
||||
export function getPinnedLinks(limit = 8): NavLink[] // 按 pinnedSort 排序
|
||||
|
||||
export function getLinks(): NavLink[] {
|
||||
return links
|
||||
}
|
||||
// src/services/categories.ts
|
||||
export function getCategories(): Category[]
|
||||
export function getCategoryById(id: string): Category | null
|
||||
|
||||
// src/services/settings.ts
|
||||
export function getSettings(): AppSettings // 解析时校验,非法数据直接抛错
|
||||
```
|
||||
|
||||
后续接 Go 后端时,只需要修改 service 层:
|
||||
所有函数返回数据副本,避免调用方改动模块级数据。`settings.json` 在模块加载时经 `parseSettings` 逐字段校验,类型不符直接抛 `TypeError`,问题在构建/测试阶段即暴露。
|
||||
|
||||
```ts
|
||||
export async function getLinks() {
|
||||
const res = await fetch("/api/links")
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to fetch links")
|
||||
}
|
||||
return res.json()
|
||||
}
|
||||
```
|
||||
数据源切换的迁移方案另行规划;当前已确保页面组件只依赖 service 接口,不直接依赖数据文件。
|
||||
|
||||
这样页面组件不用大规模重构。
|
||||
JSON 缺少编译期类型检查,用 **Vitest 数据校验测试**兜底:`src/data/data.test.ts` 校验 `data/*.json` 满足 `Category[]` / `NavLink[]` / `AppSettings` 结构(必填字段齐全、类型正确、categoryId 存在于分类中、URL 唯一且不含会话参数等),`src/services/services.test.ts` 校验 service 行为。
|
||||
|
||||
JSON 缺少编译期类型检查,用 **Vitest 数据校验测试**兜底:校验 `data/*.json` 满足 `Category[]` / `NavLink[]` / `AppSettings` 结构(必填字段齐全、类型正确、categoryId 存在于分类中),字段拼错或漏填在测试阶段即报错。
|
||||
### 5.4 后端技术栈(未来阶段)
|
||||
|
||||
### 5.4 后端技术栈(后续阶段)
|
||||
|
||||
后端由 Go 实现,数据库 PostgreSQL。
|
||||
计划由 Go 实现,数据库选用 PostgreSQL(决策 #13,未实施)。
|
||||
|
||||
候选组合:
|
||||
|
||||
@@ -154,6 +162,16 @@ 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. 数据结构
|
||||
@@ -180,6 +198,8 @@ icon: 图标名称(侧边栏,lucide 图标名)
|
||||
sort: 排序值
|
||||
```
|
||||
|
||||
当前数据共 8 个分类:AI 工具、开发工具、设计资源、学习资料、效率工具、娱乐媒体、游戏资料、生活服务。
|
||||
|
||||
### 6.2 链接数据
|
||||
|
||||
```ts
|
||||
@@ -212,6 +232,8 @@ keywords: 本地搜索关键词
|
||||
sort: 分类内排序值
|
||||
```
|
||||
|
||||
当前数据共 51 条链接,各分类均有内容。首页常用链接 6 条(按 `pinnedSort`):GitHub、ChatGPT、Gmail、Notion、YouTube、Bilibili;`getPinnedLinks` 默认上限 8 条,取 `min(pinned 数量, 8)`。
|
||||
|
||||
### 6.3 设置数据
|
||||
|
||||
```ts
|
||||
@@ -234,6 +256,8 @@ sort: 分类内排序值
|
||||
}
|
||||
```
|
||||
|
||||
> **注意**:`theme` 与 `background` 目前仅作为数据模型与校验存在(`services/settings.ts` 解析校验,`theme` 当前仅接受 `"light-glass"`),页面未消费这两个字段,**不驱动样式**。界面视觉为固定深色玻璃,见 5.5。页面实际消费的字段是 `siteName`、`search.*` 与 `sidebar.defaultCollapsed`。
|
||||
|
||||
### 6.4 统一图标方案
|
||||
|
||||
链接卡片与搜索结果的图标按以下顺序解析,逐级 fallback:
|
||||
@@ -245,11 +269,11 @@ sort: 分类内排序值
|
||||
4. 文字占位 以上全部失败 → 标题首字符 + 圆角玻璃底兜底
|
||||
```
|
||||
|
||||
侧边栏分类导航图标使用 lucide-react(如 `home` / `code` / `palette`),不参与此 fallback 链。
|
||||
侧边栏分类导航图标使用 lucide-react(如 `home` / `code` / `sparkles`),不参与此 fallback 链。
|
||||
|
||||
---
|
||||
|
||||
## 7. 未来 Go 后端 API 设计
|
||||
## 7. 未来 Go 后端 API 设计(未实现)
|
||||
|
||||
后续接入 Go 后端时,推荐 API:
|
||||
|
||||
@@ -284,7 +308,7 @@ PUT /api/settings/background
|
||||
|
||||
---
|
||||
|
||||
## 8. 数据库表设计草案
|
||||
## 8. 数据库表设计草案(未来)
|
||||
|
||||
> 草案使用跨数据库通用语法;JSON 以 TEXT 存储。PostgreSQL 下可将 `TEXT`(存 JSON)字段替换为 `JSONB` 以获得索引与查询能力。
|
||||
|
||||
@@ -339,41 +363,45 @@ CREATE TABLE settings (
|
||||
|
||||
## 9. 开发阶段
|
||||
|
||||
### 第一阶段:前端静态版(当前阶段)
|
||||
第一至第三阶段已实现(以当前代码为准),第四、五阶段为未来规划。
|
||||
|
||||
### 第一阶段:前端基础版(已完成)
|
||||
|
||||
目标:完成首页与分类页基础体验。
|
||||
|
||||
```text
|
||||
页面背景(CSS 渐变)与背景遮罩
|
||||
背景层与遮罩(样式由 CSS 定义,素材可替换)
|
||||
左侧边栏 + 折叠(localStorage 持久化)+ 导航选中态
|
||||
时间 / 日期 / 问候语
|
||||
搜索框(Google / Bing / GitHub / 本地搜索)+ Ctrl+K 快捷键
|
||||
常用链接(pinned,8 个)
|
||||
分类页路由 + 卡片网格 + 空状态
|
||||
常用链接(pinned,6 个)
|
||||
分类页路由 + 卡片网格 + 空状态组件
|
||||
桌面端响应式
|
||||
```
|
||||
|
||||
数据范围:开发工具 15 个链接 + 首页 8 个常用链接(跨分类条目归入真实分类:ChatGPT → AI 工具,Gmail / Notion → 效率工具,YouTube / Bilibili → 娱乐媒体);设计资源、学习资料、生活服务三个分类展示空状态。
|
||||
数据范围:8 个分类共 51 条链接,全部分类均有内容;空状态组件保留为数据缺失时的兜底展示。首页常用链接 6 条:GitHub、ChatGPT、Gmail、Notion、YouTube、Bilibili。
|
||||
|
||||
### 第二阶段:数据补全与体验完善
|
||||
### 第二阶段:数据补全与视觉定型(已完成)
|
||||
|
||||
```text
|
||||
补全其余 6 个分类的链接数据
|
||||
深色玻璃主题
|
||||
移动端 / 平板适配
|
||||
补全全部分类的链接数据 → 已完成(8 个分类均有内容)
|
||||
深色玻璃主题 → 已完成(固定深色玻璃,见 5.5)
|
||||
移动端 / 平板适配 → 移动端已完成(顶部横栏,见 5.5);
|
||||
平板沿用断点降级,未做专项设计
|
||||
```
|
||||
|
||||
### 第三阶段:数据访问层抽象
|
||||
### 第三阶段:数据访问层抽象(已完成)
|
||||
|
||||
```text
|
||||
封装 links / categories / settings service
|
||||
统一 TypeScript 类型
|
||||
统一错误处理与 loading 状态
|
||||
封装 links / categories / settings service → 已实现(src/services/)
|
||||
统一 TypeScript 类型 → 已实现(src/types/)
|
||||
统一错误处理与 loading 状态 → 数据解析校验失败即抛错;
|
||||
loading 状态针对远程数据,当前本地数据不涉及
|
||||
```
|
||||
|
||||
此阶段完成后,页面组件不直接关心数据来自本地配置还是 API。
|
||||
此阶段完成后,页面组件不直接关心数据来自本地配置还是 API——当前已达成:页面只依赖 service 接口。
|
||||
|
||||
### 第四阶段:Go 后端 API
|
||||
### 第四阶段:Go 后端 API(未来)
|
||||
|
||||
```text
|
||||
Go API 服务
|
||||
@@ -383,7 +411,7 @@ links / categories / settings 查询接口
|
||||
Docker 部署预留
|
||||
```
|
||||
|
||||
### 第五阶段:管理能力
|
||||
### 第五阶段:管理能力(未来)
|
||||
|
||||
```text
|
||||
新增 / 编辑 / 删除链接
|
||||
@@ -398,31 +426,47 @@ Docker 部署预留
|
||||
|
||||
## 10. 项目目录结构
|
||||
|
||||
实际目录按 feature-first 组织:
|
||||
|
||||
```text
|
||||
polaris/
|
||||
public/
|
||||
backgrounds/ # 背景素材(可随时替换,不作为设计基线)
|
||||
brand/ # 站点 Logo
|
||||
icons/ # 自定义 SVG 图标(icon: "local:xxx" 引用)
|
||||
logo.svg
|
||||
|
||||
src/
|
||||
app/
|
||||
layout.tsx # 背景层 + 字体
|
||||
page.tsx # 首页
|
||||
categories/
|
||||
[categoryId]/
|
||||
page.tsx # 分类页(generateStaticParams 生成)
|
||||
globals.css # Tailwind + CSS 变量(浅色值,深色预留覆盖位)
|
||||
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/
|
||||
layout/ # AppShell / Sidebar / SidebarNavItem
|
||||
home/ # ClockGreeting / SearchBox / SearchResults / QuickLinks
|
||||
category/ # CategoryHeader / ToolGrid / ToolCard
|
||||
common/ # SiteIcon(四级 fallback)/ GlassPanel
|
||||
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
|
||||
services/ # links.ts / categories.ts / settings.ts / search.ts
|
||||
# + data.test.ts(结构校验)
|
||||
services/ # links.ts / categories.ts / settings.ts + services.test.ts
|
||||
types/ # navigation.ts / settings.ts / search.ts
|
||||
lib/ # cn.ts / greeting.ts / local-search.ts / search-providers.ts / icon.ts(纯函数)
|
||||
lib/ # utils.ts(cn)
|
||||
|
||||
next.config.ts # output: 'standalone'
|
||||
vitest.config.ts
|
||||
@@ -432,4 +476,4 @@ polaris/
|
||||
|
||||
## 11. 最终目标
|
||||
|
||||
> 打开 Polaris 后,用户可以立刻搜索、立刻访问常用网站,也可以通过侧边栏进入分类页面。页面具有个人氛围,但保持干净、高效、克制。第一版数据本地配置,后续平滑切换到 Go 后端。
|
||||
> 打开 Polaris 后,用户可以立刻搜索、立刻访问常用网站,也可以通过侧边栏进入分类页面。页面具有个人氛围,但保持干净、高效、克制。当前数据内置于本地 JSON 配置,页面统一经 service 层取数。
|
||||
|
||||
@@ -6,6 +6,43 @@
|
||||
>
|
||||
> 配套设计图:[Polaris 页面设计图](../assets/Polaris页面设计图.png)(示意图,视觉细节以本文档文字规范为准)
|
||||
|
||||
## 0. 当前实现状态(2026-10-14 校对)
|
||||
|
||||
本章为现状说明。第 1–13 章保留立项时的历史设计意图,其中「浅色玻璃」「CSS 渐变背景」「仅适配桌面端」等表述均为历史方案,不代表当前实现;样式细节以本章与 `src/app/globals.css` 为准。
|
||||
|
||||
**视觉主题**:当前为深色玻璃主题(`color-scheme: dark`),没有浅色/深色切换。稳定的设计 tokens:
|
||||
|
||||
```css
|
||||
--background: #101a2a;
|
||||
--text-primary: #f4f7fc;
|
||||
--text-secondary: #d7e2f0;
|
||||
--text-muted: #bfd0e4;
|
||||
--primary: #a9c9ff;
|
||||
--primary-hover: #d3e3ff;
|
||||
--primary-soft: rgba(147, 190, 255, 0.15);
|
||||
--border-glass: rgba(222, 234, 255, 0.17);
|
||||
--radius-card: 20px;
|
||||
--radius-panel: 24px;
|
||||
```
|
||||
|
||||
**玻璃表面**:侧边栏、搜索框、常用链接卡片均为深色半透明渐变(`--surface-sidebar` / `--surface-search` / `--surface-card` / `--surface-card-hover`),配合 `backdrop-filter: blur(16px - 20px) saturate(108% - 134%)` 与带内描边高光的软阴影,形成悬浮玻璃面板感。
|
||||
|
||||
**背景**:背景素材可整体替换(用户会不定期更换),背景层的显示与遮罩样式由现有 CSS 管理——`body` 背景层(fixed + cover)之上叠加一层固定的深色渐变遮罩保证前景可读;素材的类型、路径与画面内容**不属于设计规范**。`settings.theme` 与 `settings.background` 目前仅有数据模型与校验逻辑,不驱动任何样式。
|
||||
|
||||
**响应式(已实现)**:
|
||||
|
||||
```text
|
||||
桌面默认:侧栏展开 240px / 折叠 72px,常用链接 4 列
|
||||
≤1199px:侧栏展开宽度 220px,常用链接 2 列
|
||||
≤767px:侧边栏变为顶部横栏(Logo + 横向滚动导航,折叠按钮隐藏),
|
||||
首页与分类页改为顶部横栏下方的常规滚动布局
|
||||
≤420px:常用链接 1 列,搜索快捷键提示隐藏
|
||||
```
|
||||
|
||||
另已支持 `prefers-reduced-motion` 降低动效。
|
||||
|
||||
**数据现状**:8 个分类(含后增的「游戏资料」)、51 条链接、6 条 pinned 常用链接。
|
||||
|
||||
## 1. 页面定位
|
||||
|
||||
Polaris 首页是一个**浏览器起始页**,不是后台管理系统,也不是信息密集型 Dashboard。
|
||||
@@ -40,6 +77,8 @@ Polaris 首页是一个**浏览器起始页**,不是后台管理系统,也
|
||||
|
||||
> 轻量玻璃感 + 极简浏览器起始页 + 柔和个人空间感
|
||||
|
||||
> 2026-10-14 实现状态:该方向已落地,但配色从浅色玻璃改为**深色玻璃**(深蓝底、冷白文字,见第 0 章);关键词中「玻璃感、低饱和、可换背景、安静」依然成立。
|
||||
|
||||
设计关键词:
|
||||
|
||||
```text
|
||||
@@ -136,6 +175,8 @@ AI 工具
|
||||
生活服务
|
||||
```
|
||||
|
||||
> 2026-10-14 实现状态:当前侧边栏为「首页 + 8 个分类」,新增了「游戏资料」分类,上表为立项时的 7 分类历史列表;折叠时导航项只显示图标并提供 Tooltip,移动端顶部横栏下不显示折叠按钮。
|
||||
|
||||
「首页」指向 `/`,各分类指向 `/categories/{categoryId}`。当前路由对应的导航项处于选中态,选中样式见[分类页设计文档](./Polaris%20分类页设计文档.md) 6.2 节。
|
||||
|
||||
暂时不展示:
|
||||
@@ -204,6 +245,8 @@ box-shadow: 0 24px 80px rgba(15, 23, 42, 0.1);
|
||||
border-radius: 24px;
|
||||
```
|
||||
|
||||
> 2026-10-14 实现状态:上方浅色样式为历史方案。当前侧边栏为深色渐变玻璃(`--surface-sidebar`,blur(20px) saturate(108%),`--shadow-panel` 阴影),悬浮边距仍为 20px;宽度见第 0 章响应式。
|
||||
|
||||
边栏与页面边缘保持一定距离,形成悬浮玻璃面板感。
|
||||
|
||||
推荐边距:
|
||||
@@ -245,6 +288,8 @@ font-weight: 700;
|
||||
color: #0f172a;
|
||||
```
|
||||
|
||||
> 2026-10-14 实现状态:当前问候语字号为 clamp(44px, 4.2vw, 56px)、冷白色(rgba(248, 250, 252, 0.98))并带文字投影,日期为 rgba(241, 245, 249, 0.86);上方 #0f172a / #64748b 为历史浅色方案。
|
||||
|
||||
日期样式:
|
||||
|
||||
```css
|
||||
@@ -311,7 +356,7 @@ height: 68px;
|
||||
高度:64px - 72px
|
||||
```
|
||||
|
||||
> 移动端尺寸第一版不定义,移动端整体适配为后续阶段工作。
|
||||
> 历史说明:立项时移动端尺寸未定义。当前移动端已适配:搜索框宽度随容器收缩,≤420px 时隐藏快捷键提示(见第 0 章)。
|
||||
|
||||
### 6.4 搜索框视觉样式
|
||||
|
||||
@@ -325,6 +370,8 @@ border-radius: 24px;
|
||||
box-shadow: 0 20px 60px rgba(15, 23, 42, 0.12);
|
||||
```
|
||||
|
||||
> 2026-10-14 实现状态:上方浅色样式为历史方案。当前搜索框为深色玻璃(`--surface-search` 渐变 + blur(20px) saturate(108%)),聚焦时出现 rgba(147, 197, 253, 0.17) 的 focus 环;尺寸仍为最大 760px × 68px。
|
||||
|
||||
placeholder 统一为:
|
||||
|
||||
```text
|
||||
@@ -480,6 +527,8 @@ border-radius: 20px;
|
||||
box-shadow: 0 20px 60px rgba(15, 23, 42, 0.12);
|
||||
```
|
||||
|
||||
> 2026-10-14 实现状态:上方浅色样式为历史方案。当前结果面板为更实的深色玻璃 rgba(19, 31, 49, 0.95),最大高度 min(520px, 100vh − 220px),选中项使用 `--primary-soft` 底色。
|
||||
|
||||
列表项内容:
|
||||
|
||||
```text
|
||||
@@ -525,6 +574,8 @@ Bilibili
|
||||
|
||||
实际项目中根据个人习惯调整。
|
||||
|
||||
> 2026-10-14 实现状态:当前 pinned 链接为 6 条,按 `pinnedSort` 排序展示:GitHub、ChatGPT、Gmail、Notion、YouTube、Bilibili;服务层默认上限仍为 8 条(`getPinnedLinks(limit = 8)`)。下文「第一版展示 8 个常用链接」及 8 条 pinned 的数据构成为立项时的历史方案。
|
||||
|
||||
数据来源:`pinned: true` 的链接,按 `pinnedSort` 排序取前 8 个;超出 8 个时按 4 列网格换行展示。`sort` 仅用于分类内排序,避免首页顺序与分类页顺序相互约束。
|
||||
|
||||
第一版数据构成(8 条 pinned 分布于各分类,归入真实分类):
|
||||
@@ -576,6 +627,8 @@ border-radius: 20px;
|
||||
box-shadow: 0 14px 40px rgba(15, 23, 42, 0.1);
|
||||
```
|
||||
|
||||
> 2026-10-14 实现状态:上方浅色样式为历史方案。当前卡片为深色玻璃(`--surface-card` + blur(16px) saturate(134%)),高 76px、圆角 20px,hover 上浮与过渡不变。
|
||||
|
||||
推荐尺寸:
|
||||
|
||||
```text
|
||||
@@ -622,6 +675,8 @@ Polaris 支持背景自定义的设计预留。
|
||||
|
||||
**第一版默认背景为 CSS 低饱和青蓝渐变**(模拟晨雾 / 天空氛围),不依赖图片资产。
|
||||
|
||||
> **2026-10-14 实现状态(重要)**:背景已不再是 CSS 渐变,改为可替换的背景素材层 + 深色遮罩(由 `body` 背景层加载,见第 0 章);背景层显示与遮罩样式由现有 CSS 管理,素材的类型、路径、画面与题材由用户自行选择、不定期更换,**不写入设计规范**。下方「图片背景建议」仅作当时的设计联想,不构成约束。`settings.background.type` 目前仅有数据模型与校验,未接入渲染;「CSS 低饱和青蓝渐变」为历史方案。
|
||||
|
||||
后续支持:
|
||||
|
||||
```text
|
||||
@@ -650,6 +705,8 @@ Polaris 支持背景自定义的设计预留。
|
||||
|
||||
由于用户未来可能更换任意背景,因此需要增加背景遮罩层,保证内容可读。
|
||||
|
||||
> 2026-10-14 实现状态:遮罩已实现,但形态与下方两侧历史值不同——当前为固定深色渐变(顶部 rgba(5, 12, 25, 0.43) → 中部 0.37 → 底部 0.47,无 backdrop blur);浅色遮罩值为历史方案,「深色主题(后续阶段预留)」未按该形式落地。
|
||||
|
||||
浅色主题:
|
||||
|
||||
```css
|
||||
@@ -668,9 +725,9 @@ backdrop-filter: blur(8px);
|
||||
|
||||
## 10. 配色系统
|
||||
|
||||
默认采用浅色玻璃主题。
|
||||
> 2026-10-14 实现状态:当前默认为**深色玻璃主题**(无主题切换),tokens 直接定义在 `globals.css` 的 `:root`,未使用 `[data-theme]` 覆盖;`settings.theme` 未接入样式。现行值见第 0 章;下方「推荐 CSS 变量」为立项时的浅色方案,仅作历史参考。
|
||||
|
||||
推荐 CSS 变量:
|
||||
历史方案(浅色玻璃)推荐 CSS 变量:
|
||||
|
||||
```css
|
||||
:root {
|
||||
@@ -694,7 +751,7 @@ backdrop-filter: blur(8px);
|
||||
}
|
||||
```
|
||||
|
||||
变量按双主题友好方式组织:第一版只实现浅色值;深色玻璃主题为后续阶段,届时以 `[data-theme="dark"]` 覆盖同名变量,组件无需改动。
|
||||
> 历史说明:上段为立项时的双主题规划。实际实现未走 `[data-theme]` 覆盖路线,深色值自当前版本起直接作为默认主题(见第 0 章与 `globals.css`)。
|
||||
|
||||
---
|
||||
|
||||
@@ -716,6 +773,8 @@ font-family:
|
||||
sans-serif;
|
||||
```
|
||||
|
||||
> 2026-10-14 实现状态:当前字体栈以 Avenir Next / Avenir 开头,未引入 Inter;中文回退与上方一致。
|
||||
|
||||
字体原则:
|
||||
|
||||
```text
|
||||
@@ -775,6 +834,8 @@ font-family:
|
||||
第一版仅实现浅色主题、仅适配桌面端
|
||||
```
|
||||
|
||||
> 历史说明:末行「仅浅色主题、仅适配桌面端」为第一版约束,已被后续实现取代——当前为深色玻璃默认主题,移动端适配已完成。
|
||||
|
||||
最终目标:
|
||||
|
||||
> 打开 Polaris 后,用户可以立刻搜索、立刻访问常用网站,也可以通过侧边栏进入分类页面。页面具有个人氛围,但保持干净、高效、克制。
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Polaris 文档与当前实现对齐(2026-10-14)
|
||||
|
||||
状态:用户已确认调整后的方案;设计文档和部署说明已更新(2026-10-14)。未修改产品代码、配置或 CI 工作流。
|
||||
|
||||
## 范围
|
||||
|
||||
1. 更新 `docs/design/Polaris 项目说明文档.md`:将当前部署方式统一为 standalone;依据现有 CSS 描述深色玻璃视觉、移动端布局、8 类 51 条链接及 6 条常用链接;更正阶段清单与目录结构;明确 `settings.theme`、`settings.background` 仅有数据模型与校验,未驱动样式。背景素材属于可更换内容,不写死图片路径、画面类型或具体背景外观。
|
||||
2. 更新 `docs/design/Polaris 首页设计文档.md`、`Polaris 分类页设计文档.md`:在保留历史设计意图的同时,按现有代码描述深色玻璃风格和移动端顶部横栏;区分尚未实现的双主题、背景配置生效与已实现的分类网格三档断点(≥1280px 三列、1000–1279px 两列、≤999px 单列),不规定具体背景素材及其内容。
|
||||
3. 对日期命名的 `docs/design/2026-08-26-Polaris-首页实现规格.md` 增加历史规格状态说明和现状索引,不逐行重写历史验收要求;历史 `docs/superpowers/plans/` 保持原貌。
|
||||
4. 修订 `deploy/README.md` 的触发方式和镜像摘要说明:仅推送 `polaris-v*` 标签才运行 CI;验证通过后发布带完整 commit SHA 的镜像;workflow 已实现摘要输出,但 registry、runner 与服务器行为仍待现场验证。明确 Compose 目前只要求镜像变量非空,digest 固定是部署操作约定而非自动校验。
|
||||
|
||||
## 边界与验证
|
||||
|
||||
- 不修改应用代码、配置数据、工作流或标签触发条件;不处理未来 Go API。
|
||||
- 不以当前背景文件或壁纸内容作为设计基线;用户可不定期替换背景素材,视觉描述以现有 CSS 中稳定的色彩、表面与交互样式为准。
|
||||
- 不碰仓库 `note/` 中已有的删除/新增改动,亦不处理仓库外或已删除的历史笔记。
|
||||
- 编辑前检查目标文件缩进和换行形式;完成后检查文档差异、搜索剩余过时表述并核对 Git 工作树。纯文档更新不要求构建或测试。
|
||||
Reference in New Issue
Block a user