自定义导航栏
点击按钮,AI 将为你生成这篇文章的摘要
自定义导航栏
本文档详细介绍如何为 Firefly 博客添加自定义导航栏菜单项,以”说说 (Talks)“功能为例。
目录
- 目录结构总览
- Step 1:创建内容集合
- Step 2:创建 Markdown 文件
- Step 3:添加枚举值
- Step 4:添加页面开关
- Step 5:添加导航栏配置
- Step 6:添加预设映射
- Step 7:添加国际化键值
- Step 8:创建页面路由
- 注意事项
- 快速参考模板
目录结构总览
src/├── content/│ ├── content.config.ts # 内容集合定义│ └── spec/│ └── talks.md # ✅ 说说内容文件├── types/│ └── config.ts # ✅ LinkPreset 枚举定义├── constants/│ └── link-presets.ts # ✅ 预设映射表├── i18n/│ ├── i18nKey.ts # ✅ 国际化键值│ ├── translation.ts # 国际化翻译│ └── languages/│ ├── zh_CN.ts # ✅ 中文翻译│ ├── en.ts # ✅ 英文翻译│ ├── ja.ts # ✅ 日文翻译│ ├── zh_TW.ts # ✅ 繁体中文翻译│ └── ru.ts # ✅ 俄文翻译├── config/│ ├── siteConfig.ts # ✅ 页面开关│ └── navBarConfig.ts # ✅ 导航栏配置├── pages/│ └── talks.astro # ✅ 页面路由└── components/ └── layout/ └── Navbar.astro # 导航栏组件(无需修改)Step 1:创建内容集合
文件:src/content.config.ts
在文件中添加 spec 集合定义(如果还没有的话):
import { defineCollection } from "astro:content";import { glob } from "astro/loaders";import { z } from "astro/zod";
const postsCollection = defineCollection({ loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/posts" }), schema: z.object({ title: z.string(), published: z.date(), updated: z.date().optional(), draft: z.boolean().optional().default(false), description: z.string().optional().default(""), image: z.string().optional().default(""), tags: z.array(z.string()).optional().default([]), category: z.string().optional().nullable().default(""), lang: z.string().optional().default(""), pinned: z.boolean().optional().default(false), author: z.string().optional().default(""), sourceLink: z.string().optional().default(""), licenseName: z.string().optional().default(""), licenseUrl: z.string().optional().default(""), comment: z.boolean().optional().default(true), password: z.string().optional().default(""), passwordHint: z.string().optional().default(""), abbrlink: z.string().optional().default(""), prevTitle: z.string().default(""), prevSlug: z.string().default(""), nextTitle: z.string().default(""), nextSlug: z.string().default(""), }),});
// ✅ spec 集合 - 用于说说、相册等特殊页面const specCollection = defineCollection({ loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/spec" }), schema: z.object({}), // schema 为空,灵活度高});
export const collections = { posts: postsCollection, spec: specCollection,};Step 2:创建 Markdown 文件
文件:src/content/spec/talks.md
创建说说内容文件:
---title: 说说date: 2026-05-27---
# 我的说说
这里可以写你的日常动态、碎碎念,支持所有 Markdown 语法:
- 2026-05-27:终于把博客导航栏的说说菜单配置好了!- 2026-05-26:相册的加密功能终于跑通了,开心~- 2026-05-25:今天开始学习 Astro 博客搭建,加油!
> 以后可以在这里随时更新,内容和页面完全分离,维护超方便~说明:
- Frontmatter 字段可选(
spec集合 schema 为空) - 建议保留
title和date便于管理 - 支持标准 Markdown 语法
Step 3:添加枚举值
文件:src/types/config.ts
在 LinkPreset 枚举中添加新值:
export enum LinkPreset { Home = 0, Archive = 1, About = 2, Friends = 3, Sponsor = 4, Guestbook = 5, Bangumi = 6, Gallery = 7, Talk = 8, // ✅ 添加说说枚举值}注意事项:
- 枚举值必须唯一(不能与其他值重复)
- 命名要简洁明了(
Talk而不是Talks) - 后续所有引用必须保持一致
Step 4:添加页面开关
文件:src/config/siteConfig.ts
在 pages 对象中添加开关:
export const siteConfig = { // ... 其他配置 pages: { home: true, archive: true, about: true, friends: true, sponsor: true, guestbook: true, bangumi: true, gallery: true, talks: true, // ✅ 添加说说开关 // ... }}说明:
true= 在导航栏显示false= 在导航栏隐藏- 开关关闭时,导航栏和页面都不会生成
Step 5:添加导航栏配置
文件:src/config/navBarConfig.ts
在 getDynamicNavBarConfig 函数中添加菜单项:
const getDynamicNavBarConfig = (): NavBarConfig => { const links: (NavBarLink | LinkPreset)[] = [ LinkPreset.Home, LinkPreset.Archive, ];
// 根据配置决定是否添加留言板 if (siteConfig.pages.guestbook) { links.push(LinkPreset.Guestbook); }
// 我的及其子菜单 links.push({ name: "动态", url: "/my/", icon: "material-symbols:person", children: [ // 根据配置决定是否添加相册 ...(siteConfig.pages.gallery ? [LinkPreset.Gallery] : []), // ✅ 根据配置决定是否添加说说 ...(siteConfig.pages.talks ? [LinkPreset.Talk] : []), // 根据配置决定是否添加番组计划 ...(siteConfig.pages.bangumi ? [LinkPreset.Bangumi] : []), ], });
// 关于及其子菜单 links.push({ name: "关于", url: "/content/", icon: "material-symbols:info", children: [ ...(siteConfig.pages.sponsor ? [LinkPreset.Sponsor] : []), ...(siteConfig.pages.friends ? [LinkPreset.Friends] : []), LinkPreset.About, ], });
return { links } as NavBarConfig;};说明:
- 使用展开运算符
...根据开关决定是否添加 - 可以放在
children数组中作为子菜单 - 也可以直接放在
links数组中作为一级菜单
Step 6:添加预设映射
文件:src/constants/link-presets.ts
在 LinkPresets 对象中添加映射:
import I18nKey from "@i18n/i18nKey";import { i18n } from "@i18n/translation";import { LinkPreset, type NavBarLink } from "@/types/config";
export const LinkPresets: { [key in LinkPreset]: NavBarLink } = { [LinkPreset.Home]: { name: i18n(I18nKey.home), url: "/", icon: "material-symbols:home", }, [LinkPreset.About]: { name: i18n(I18nKey.about), url: "/about/", icon: "material-symbols:person", }, [LinkPreset.Archive]: { name: i18n(I18nKey.archive), url: "/archive/", icon: "material-symbols:archive", }, [LinkPreset.Friends]: { name: i18n(I18nKey.friends), url: "/friends/", icon: "material-symbols:group", }, [LinkPreset.Sponsor]: { name: i18n(I18nKey.sponsor), url: "/sponsor/", icon: "material-symbols:favorite", }, [LinkPreset.Guestbook]: { name: i18n(I18nKey.guestbook), url: "/guestbook/", icon: "material-symbols:chat", }, [LinkPreset.Bangumi]: { name: i18n(I18nKey.bangumi), url: "/bangumi/", icon: "material-symbols:movie", }, [LinkPreset.Gallery]: { name: i18n(I18nKey.gallery), url: "/gallery/", icon: "material-symbols:photo-library", }, // ✅ 添加说说预设映射 [LinkPreset.Talk]: { name: i18n(I18nKey.talks), url: "/talks/", icon: "material-symbols:chat", },};字段说明:
name:显示的菜单名称(通过 i18n 国际化)url:点击后跳转的路径icon:菜单图标(使用 Material Symbols 图标)
Step 7:添加国际化键值
文件:src/i18n/i18nKey.ts
在枚举中添加国际化键值:
export enum I18nKey { // ... 其他键值 home = "home", archive = "archive", about = "about", friends = "friends", sponsor = "sponsor", guestbook = "guestbook", bangumi = "bangumi", gallery = "gallery", talks = "talks", // ✅ 说说标题 talksDescription = "talksDescription", // ✅ 说说描述 // ...}注意:需要同时添加标题和描述两个键值。
Step 8:创建页面路由
文件:src/pages/talks.astro
创建说说页面:
---import MainGridLayout from "@/layouts/MainGridLayout.astro";import { getEntry, render } from "astro:content";import Markdown from "@components/common/Markdown.astro";import I18nKey from "@/i18n/i18nKey";import { i18n } from "@/i18n/translation";
// 读取 src/content/spec/talks.mdconst talksPost = await getEntry("spec", "talks");
// 检查页面是否可用if (!talksPost) { throw new Error("Talks page content not found");}
const { Content } = await render(talksPost);---
<MainGridLayout title={i18n(I18nKey.talks)} description={i18n(I18nKey.talksDescription)}> <div class="flex w-full rounded-(--radius-large) overflow-hidden"> <div class="card-base z-10 px-9 py-6 relative w-full"> <Markdown class="mt-2"> <Content /> </Markdown> </div> </div></MainGridLayout>注意事项:
- import 路径必须包含
.astro扩展名 getEntry("spec", "talks")的第一个参数是集合名,第二个是文件名(不含扩展名)- 使用
render函数渲染 Markdown 内容
注意事项
常见错误
| 错误 | 原因 | 解决方案 |
|---|---|---|
LinkPreset.Talks is not defined | 枚举名不一致 | 检查 config.ts 中的枚举名是否为 Talk |
Cannot resolve import | 缺少 .astro 扩展名 | import 路径加上 .astro |
Cannot read properties of undefined | link-presets.ts 缺少映射 | 添加 LinkPreset.Talk 的映射 |
| 导航栏不显示菜单 | 页面开关为 false | 检查 siteConfig.pages.talks |
| 页面 404 | 路由文件不存在 | 检查 src/pages/talks.astro |
| 导航栏显示空白文字 | i18n 枚举键名大小写不匹配 | 见下方 i18n 键名大小写问题 |
i18n 键名大小写问题
问题描述:添加新功能后,导航栏菜单或页面标题显示为空白,没有文字。
根本原因:src/i18n/i18nKey.ts 中枚举成员名与 link-presets.ts、talks.astro 等文件中的引用名大小写不一致。TypeScript 枚举区分大小写,I18nKey.Talks 和 I18nKey.talks 是不同的键,导致 i18n() 函数找不到翻译,返回空字符串。
错误示例(枚举成员是大写,引用是小写):
// ❌ i18nKey.ts — 枚举成员名是大写 Talkexport enum I18nKey { Talks = "talks", // 大写 T TalksDescription = "talksDescription",}
// ❌ link-presets.ts — 引用的是小写 talksname: i18n(I18nKey.talks), // 未定义的键 → 返回空字符串
// ❌ talks.astro — 同样引用小写title={i18n(I18nKey.talks)}正确写法(统一使用小写 camelCase):
// ✅ i18nKey.ts — 枚举成员名使用小写 camelCase(与其他键保持一致)export enum I18nKey { home = "home", archive = "archive", about = "about", friends = "friends", gallery = "gallery", talks = "talks", // 小写 t talksDescription = "talksDescription",}
// ✅ link-presets.ts — 引用匹配name: i18n(I18nKey.talks),
// ✅ talks.astro — 引用匹配title={i18n(I18nKey.talks)}同时别忘了添加翻译:
// ✅ src/i18n/languages/zh_CN.ts[Key.talks]: "说说",[Key.talksDescription]: "我的日常动态和碎碎念",
// ✅ src/i18n/languages/en.ts[Key.talks]: "Talks",[Key.talksDescription]: "My daily updates and thoughts",排查技巧:如果导航栏或页面标题显示空白,打开浏览器控制台查看是否有 i18n 相关警告,并检查 i18nKey.ts 中枚举成员名是否与所有引用处大小写一致。
完整检查清单
-
src/content.config.ts中定义了spec集合 -
src/content/spec/talks.md文件已创建 -
src/types/config.ts中添加了Talk = 8 -
src/config/siteConfig.ts中talks: true -
src/config/navBarConfig.ts中添加了菜单项 -
src/constants/link-presets.ts中添加了预设映射 -
src/i18n/i18nKey.ts中添加了talks和talksDescription -
src/pages/talks.astro页面文件已创建
运行测试
# 开发模式pnpm dev
# 构建生产版本pnpm build
# 预览生产版本pnpm preview快速参考模板
如果你想添加一个新功能(如”笔记”),只需按以下步骤操作:
1. 创建内容文件
文件:src/content/spec/notes.md
---title: 笔记date: 2026-05-27---
# 我的笔记
这里记录学习笔记和技术心得...2. 添加枚举值
文件:src/types/config.ts
export enum LinkPreset { // ... Talk = 8, Notes = 9, // ✅ 新增}3. 添加页面开关
文件:src/config/siteConfig.ts
pages: { // ... talks: true, notes: true, // ✅ 新增}4. 添加导航栏配置
文件:src/config/navBarConfig.ts
children: [ ...(siteConfig.pages.gallery ? [LinkPreset.Gallery] : []), ...(siteConfig.pages.talks ? [LinkPreset.Talk] : []), ...(siteConfig.pages.notes ? [LinkPreset.Notes] : []), // ✅ 新增],5. 添加预设映射
文件:src/constants/link-presets.ts
[LinkPreset.Notes]: { name: i18n(I18nKey.notes), url: "/notes/", icon: "material-symbols:note",},6. 添加国际化键值
文件:src/i18n/i18nKey.ts
notes = "notes",notesDescription = "notesDescription",7. 创建页面路由
文件:src/pages/notes.astro
---import MainGridLayout from "@/layouts/MainGridLayout.astro";import { getEntry, render } from "astro:content";import Markdown from "@components/common/Markdown.astro";import I18nKey from "@/i18n/i18nKey";import { i18n } from "@/i18n/translation";
const notesPost = await getEntry("spec", "notes");if (!notesPost) { throw new Error("Notes page content not found");}const { Content } = await render(notesPost);---
<MainGridLayout title={i18n(I18nKey.notes)} description={i18n(I18nKey.notesDescription)}> <div class="flex w-full rounded-(--radius-large) overflow-hidden"> <div class="card-base z-10 px-9 py-6 relative w-full"> <Markdown class="mt-2"> <Content /> </Markdown> </div> </div></MainGridLayout>总结
添加自定义导航栏菜单项需要修改 8 个文件:
| 序号 | 文件 | 修改内容 |
|---|---|---|
| 1 | src/content.config.ts | 定义内容集合(通常已存在) |
| 2 | src/content/spec/xxx.md | 创建 Markdown 内容文件 |
| 3 | src/types/config.ts | 添加 LinkPreset 枚举值 |
| 4 | src/config/siteConfig.ts | 添加 pages.xxx 开关 |
| 5 | src/config/navBarConfig.ts | 添加导航栏菜单项 |
| 6 | src/constants/link-presets.ts | 添加预设映射 |
| 7 | src/i18n/i18nKey.ts | 添加国际化键值(注意小写 camelCase) |
| 8 | src/pages/xxx.astro | 创建页面路由文件 |
别忘了:在 src/i18n/languages/ 下的所有语言文件中添加对应的翻译条目,否则导航栏和页面标题会显示空白。
按照以上步骤操作,即可成功添加自定义导航栏菜单项。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!