From b72b14a8188eb63761a62968c47b139d7d3cd62b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E8=82=B2=E6=96=B0?= <2635131263@qq.com> Date: Mon, 19 Oct 2026 11:35:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(polaris):=20=E6=96=B0=E5=A2=9E=E4=B8=89?= =?UTF-8?q?=E4=B8=AA=E5=AF=BC=E8=88=AA=E7=BD=91=E7=AB=99=E5=B9=B6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=B8=B8=E6=88=8F=E8=B5=84=E6=96=99=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plans/2026-10-19-navigation-additions.md | 30 +++++++++++ .../nextjs/polaris/src/data/categories.json | 2 +- ts/web/nextjs/polaris/src/data/data.test.ts | 52 +++++++++++++++++-- ts/web/nextjs/polaris/src/data/links.json | 30 +++++++++++ .../components/category-icon.test.tsx | 29 +++++++++++ .../navigation/components/category-icon.tsx | 2 + .../polaris/src/services/services.test.ts | 1 + 7 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 ts/web/nextjs/polaris/docs/plans/2026-10-19-navigation-additions.md create mode 100644 ts/web/nextjs/polaris/src/features/navigation/components/category-icon.test.tsx diff --git a/ts/web/nextjs/polaris/docs/plans/2026-10-19-navigation-additions.md b/ts/web/nextjs/polaris/docs/plans/2026-10-19-navigation-additions.md new file mode 100644 index 0000000..787828a --- /dev/null +++ b/ts/web/nextjs/polaris/docs/plans/2026-10-19-navigation-additions.md @@ -0,0 +1,30 @@ +# 新增导航与游戏资料分类图标 + +## 范围与决定 + +- 用户已明确要求执行;从干净的 `main` 新建 `feat/polaris-navigation-additions` 分支,不提交代码。 +- 新增 OpenRouter(AI 工具)、菜鸟教程(学习资料)、Lucide(设计资源),保持原有条目不变。 +- 新条目沿用现有数据结构,填写简短描述、关键词和分类内排序,不置顶;图标沿用既有 favicon 与首字母兜底,不新增依赖或资源。 +- 仅将游戏资料分类的 `icon` 改为 `book-search`,前端映射增加 Lucide 的 `BookSearch`,保留学习资料的 `BookOpen`。 +- 不调整布局、样式或其他分类图标。 + +## 执行顺序与责任 + +1. 协调者创建分支并记录计划(已完成)。 +2. fixer 负责 `src/data/links.json`、`src/data/categories.json`、`src/features/navigation/components/category-icon.tsx` 及必要的局部回归测试;编辑前确认编码、缩进与换行,阅读适用的 Next.js 本地文档。 +3. fixer 运行数据与图标相关测试、类型检查、修改文件的 lint 和格式检查,报告证据;协调者复核最终差异和关键行为。 + +## 验证路径与预算 + +- 新增导航:断言三个 ID/URL 唯一、分类正确、字段满足现有契约,现有条目不变。 +- 分类图标:断言游戏资料为 `book-search` 且渲染实际 `BookSearch`,学习资料仍为 `book-open`;类型检查确认当前 lucide-react 导出可用。 +- 工程检查:优先运行现有测试套件(若成本较低则全部)、`pnpm typecheck`、修改文件的 ESLint 与 Prettier 检查、`git diff --check`。 +- 不以外部站点的 favicon 网络可达性作为本次完成门槛;其失败沿用已有兜底机制。 +- 无框架/API/布局变更,不重复执行完整生产构建或截图验证,除非检查暴露相关风险。 + +## 状态 + +- 实施完成,未提交代码。 +- fixer 验证:全量测试 17 个文件、117 个用例通过;类型检查、修改文件 ESLint 与 Prettier 检查通过。环境使用 `corepack pnpm` 执行项目命令。 +- 协调者复核:原有 51 条链接与 HEAD 完全一致,恰好新增 3 条;分类数据除游戏资料的 `icon` 外与 HEAD 完全一致;图标组件测试覆盖 `BookSearch`、`BookOpen` 与默认兜底;`git diff --check` 通过。 +- 外部 favicon 可达性未测试,不影响本次数据及图标映射变更结论。 diff --git a/ts/web/nextjs/polaris/src/data/categories.json b/ts/web/nextjs/polaris/src/data/categories.json index dad0b6f..df875fb 100644 --- a/ts/web/nextjs/polaris/src/data/categories.json +++ b/ts/web/nextjs/polaris/src/data/categories.json @@ -45,7 +45,7 @@ "id": "game-resources", "title": "游戏资料", "description": "游戏攻略维基、资料速查与实用规划工具。", - "icon": "book-open", + "icon": "book-search", "sort": 65 }, { diff --git a/ts/web/nextjs/polaris/src/data/data.test.ts b/ts/web/nextjs/polaris/src/data/data.test.ts index e80cd6a..e9d1753 100644 --- a/ts/web/nextjs/polaris/src/data/data.test.ts +++ b/ts/web/nextjs/polaris/src/data/data.test.ts @@ -30,9 +30,9 @@ describe("Polaris 静态数据", () => { } }) - it("包含 51 条合法链接且分类引用均存在", () => { + it("包含 54 条合法链接且分类引用均存在", () => { const categoryIds = new Set(categories.map(({ id }) => id)) - expect(links).toHaveLength(51) + expect(links).toHaveLength(54) expect(hasUniqueValues(links.map(({ id }) => id))).toBe(true) for (const link of links) { @@ -225,8 +225,8 @@ describe("Polaris 静态数据", () => { }, ] as const - it("九条新链接全部存在且总数为 51", () => { - expect(links).toHaveLength(51) + it("九条新链接全部存在且总数为 54", () => { + expect(links).toHaveLength(54) expect( expectedNewLinks.every(({ id }) => links.some((link) => link.id === id), @@ -248,4 +248,48 @@ describe("Polaris 静态数据", () => { }, ) }) + + describe("新增三条导航链接回归", () => { + const expectedNewLinks = [ + { + id: "openrouter", + title: "OpenRouter", + url: "https://openrouter.ai", + categoryId: "ai", + }, + { + id: "runoob", + title: "菜鸟教程", + url: "https://www.runoob.com/", + categoryId: "learning", + }, + { + id: "lucide", + title: "Lucide", + url: "https://lucide.dev/", + categoryId: "design", + }, + ] as const + + it("三条新链接字段与分类精确匹配且沿用 favicon 兜底", () => { + for (const expected of expectedNewLinks) { + const link = links.find(({ id }) => id === expected.id) + + expect(link).toBeDefined() + expect(link).toMatchObject({ + id: expected.id, + title: expected.title, + url: expected.url, + categoryId: expected.categoryId, + pinned: false, + }) + expect(link?.icon).toBeUndefined() + } + }) + + it("三条新链接 ID 与 URL 唯一", () => { + expect(hasUniqueValues(expectedNewLinks.map(({ id }) => id))).toBe(true) + expect(hasUniqueValues(expectedNewLinks.map(({ url }) => url))).toBe(true) + }) + }) }) diff --git a/ts/web/nextjs/polaris/src/data/links.json b/ts/web/nextjs/polaris/src/data/links.json index 7af3375..bbef66f 100644 --- a/ts/web/nextjs/polaris/src/data/links.json +++ b/ts/web/nextjs/polaris/src/data/links.json @@ -534,5 +534,35 @@ "pinned": false, "keywords": ["阿里云", "控制台", "云服务", "管理"], "sort": 100 + }, + { + "id": "openrouter", + "title": "OpenRouter", + "description": "统一大模型 API 聚合平台", + "url": "https://openrouter.ai", + "categoryId": "ai", + "pinned": false, + "keywords": ["ai", "大模型", "api", "聚合"], + "sort": 30 + }, + { + "id": "runoob", + "title": "菜鸟教程", + "description": "编程入门教程与在线学习网站", + "url": "https://www.runoob.com/", + "categoryId": "learning", + "pinned": false, + "keywords": ["教程", "编程", "学习", "入门"], + "sort": 20 + }, + { + "id": "lucide", + "title": "Lucide", + "description": "开源 SVG 图标库", + "url": "https://lucide.dev/", + "categoryId": "design", + "pinned": false, + "keywords": ["图标", "svg", "图标库", "开源"], + "sort": 50 } ] diff --git a/ts/web/nextjs/polaris/src/features/navigation/components/category-icon.test.tsx b/ts/web/nextjs/polaris/src/features/navigation/components/category-icon.test.tsx new file mode 100644 index 0000000..e649e54 --- /dev/null +++ b/ts/web/nextjs/polaris/src/features/navigation/components/category-icon.test.tsx @@ -0,0 +1,29 @@ +import { render } from "@testing-library/react" +import { describe, expect, it } from "vitest" + +import { CategoryIcon } from "./category-icon" + +describe("CategoryIcon", () => { + it("为 book-search 渲染实际的 BookSearch 图标", () => { + const { container } = render() + const svg = container.querySelector("svg") + + expect(svg).not.toBeNull() + expect(svg).toHaveClass("lucide-book-search") + }) + + it("book-open 仍渲染 BookOpen 而非 BookSearch", () => { + const { container } = render() + const svg = container.querySelector("svg") + + expect(svg).not.toBeNull() + expect(svg).toHaveClass("lucide-book-open") + expect(svg).not.toHaveClass("lucide-book-search") + }) + + it("未知图标回退到 Sparkles", () => { + const { container } = render() + + expect(container.querySelector("svg")).toHaveClass("lucide-sparkles") + }) +}) diff --git a/ts/web/nextjs/polaris/src/features/navigation/components/category-icon.tsx b/ts/web/nextjs/polaris/src/features/navigation/components/category-icon.tsx index d7db875..8ddfebc 100644 --- a/ts/web/nextjs/polaris/src/features/navigation/components/category-icon.tsx +++ b/ts/web/nextjs/polaris/src/features/navigation/components/category-icon.tsx @@ -1,5 +1,6 @@ import { BookOpen, + BookSearch, CirclePlay, Code2, Coffee, @@ -14,6 +15,7 @@ const CATEGORY_ICONS: Readonly> = { code: Code2, palette: Palette, "book-open": BookOpen, + "book-search": BookSearch, zap: Zap, "circle-play": CirclePlay, coffee: Coffee, diff --git a/ts/web/nextjs/polaris/src/services/services.test.ts b/ts/web/nextjs/polaris/src/services/services.test.ts index 5ae266e..27985a2 100644 --- a/ts/web/nextjs/polaris/src/services/services.test.ts +++ b/ts/web/nextjs/polaris/src/services/services.test.ts @@ -57,6 +57,7 @@ describe("静态数据 service", () => { "haowallpaper", "iodraw", "pixiv", + "lucide", ]) expect(getLinksByCategory("life").map(({ id }) => id)).toEqual(["amap"]) })