导航栏模块升级与新增功能
点击按钮,AI 将为你生成这篇文章的摘要
目录
- 一、概述
- 二、五层架构总览
- 三、改进一:分类独立页面
- 四、改进二:hoverTitle 标题变化
- 五、改进三:下拉菜单点击触发
- 六、改进四:Guestbook 入口统一
- 七、改进五:LinkPreset 枚举扩展
- 八、导航栏最终结构
- 九、文件变更清单
一、概述
本次对博客导航栏模块进行了五项核心改进,对标参考博客 blog.tsh520.cn 的导航栏实现,全面提升导航栏的功能性和用户体验:
| 改进项 | 说明 | 优先级 |
|---|---|---|
| 分类独立页面 | 新增 /categories/ 页面,与归档分离 | 高 |
| hoverTitle | 鼠标悬停 Logo 时标题变化为趣味文字 | 中 |
| 点击触发下拉 | 桌面端下拉菜单从 hover 改为 click | 中 |
| Guestbook 统一 | 移除重复顶级入口,仅保留在子菜单 | 低 |
| 枚举扩展 | LinkPreset 新增 Categories/Books 等 | 低 |
二、五层架构总览
导航栏系统由五层架构组成:
类型层 (config.ts) → 定义 TypeScript 类型和枚举 ↓配置层 (navBarConfig.ts) → 动态生成导航链接 ↓常量层 (link-presets.ts) → 预设链接映射 ↓组件层 (Navbar.astro 等) → 渲染 UI 组件 ↓样式层 (navbar.css / main.css) → 视觉呈现三、改进一:分类独立页面
3.1 背景
原项目只有归档页面(/archive/),分类信息通过 CategoryBar 横向滚动条展示。新增独立分类页面,提供更直观的分类浏览体验。
3.2 新增页面 src/pages/categories.astro
---import I18nKey from "@i18n/i18nKey";import { i18n } from "@i18n/translation";import { Icon } from "astro-icon/components";import MainGridLayout from "@layouts/MainGridLayout.astro";import { getCategoryList } from "@/utils/content-utils";
const categories = await getCategoryList();const totalCategories = categories.length;const totalPosts = categories.reduce((sum, cat) => sum + cat.count, 0);---
<MainGridLayout title={i18n(I18nKey.categories)}> <div class="card-base p-6 md:p-8 onload-animation"> <!-- 标题区域 --> <div class="flex items-center gap-3 mb-6"> <div class="flex items-center justify-center w-12 h-12 rounded-xl bg-(--primary) text-white"> <Icon name="material-symbols:folder-open" class="text-[1.5rem]" /> </div> <div> <h1 class="text-2xl font-bold text-black/90 dark:text-white/90"> {i18n(I18nKey.categories)} </h1> <p class="text-sm text-black/50 dark:text-white/50"> 共 {totalCategories} 个分类,{totalPosts} 篇文章 </p> </div> </div>
<!-- 分类网格 --> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"> {categories.map((cat) => ( <a href={`/archive/?category=${encodeURIComponent(cat.name)}`} class="group card-base p-4 transition-all duration-200 hover:translate-y-[-2px] hover:shadow-lg" > <div class="flex items-center gap-3"> <div class="flex items-center justify-center w-10 h-10 rounded-lg bg-(--primary)/10 text-(--primary) group-hover:bg-(--primary) group-hover:text-white transition-all duration-200"> <Icon name="material-symbols:folder" class="text-[1.25rem]" /> </div> <div class="flex-1 min-w-0"> <h3 class="font-bold text-black/80 dark:text-white/80 truncate group-hover:text-(--primary) transition-colors"> {cat.name} </h3> <p class="text-xs text-black/40 dark:text-white/40"> {cat.count} 篇文章 </p> </div> <Icon name="material-symbols:chevron-right-rounded" class="text-lg text-black/20 dark:text-white/20 group-hover:text-(--primary) transition-all group-hover:translate-x-1" /> </div> </a> ))} </div> </div></MainGridLayout>3.3 数据获取
分类数据通过 src/utils/content-utils.ts 中的 getCategoryList() 获取:
export type Category = { name: string; count: number; url: string;};
export async function getCategoryList(): Promise<Category[]> { const allBlogPosts = await getCollection<"posts">("posts", ({ data }) => { return import.meta.env.PROD ? data.draft !== true : true; }); // ... 按分类统计文章数量并排序}3.4 导航栏入口
在 src/config/navBarConfig.ts 中添加分类链接:
const links: (NavBarLink | LinkPreset)[] = [ LinkPreset.Home, LinkPreset.Categories, // ← 新增 LinkPreset.Archive, // ...];四、改进二:hoverTitle 标题变化
4.1 功能说明
鼠标悬停在导航栏 Logo 区域时,标题从正常文字变为趣味文字,鼠标移开后恢复。参考博客使用此功能增加趣味交互。
4.2 类型定义
在 src/types/config.ts 的 SiteConfig.navbar 中新增 hoverTitle 字段:
navbar: { logo?: { type: "icon" | "image" | "url"; value: string; alt?: string; }; title?: string; hoverTitle?: string; // ← 新增:鼠标悬停时显示的标题 widthFull?: boolean; menuAlign?: "left" | "center"; followTheme?: boolean; stickyNavbar?: boolean;};4.3 配置使用
在 src/config/siteConfig.ts 中设置:
navbar: { logo: { type: "image", value: "assets/images/firefly.png", alt: "🍀", }, title: "lyf:Blog", hoverTitle: "👋 别走嘛,再看看!", // ← 新增 // ...},4.4 组件实现
在 src/components/layout/Navbar.astro 中:
模板部分 — 添加 data 属性:
<div id="navbar" class="z-50" data-hover-title={navbarHoverTitle} ...> <a href={url('/')} class="btn-plain ... navbar-logo-link" data-original-title={navbarTitle}> <!-- Logo + 标题 --> </a></div>脚本部分 — 添加事件监听:
function initHoverTitle() { const navbar = document.getElementById('navbar'); if (!navbar) return; const hoverTitle = navbar.getAttribute('data-hover-title'); if (!hoverTitle) return;
const logoLink = navbar.querySelector('.navbar-logo-link'); if (!logoLink) return;
const logoText = logoLink.querySelector('span, div'); if (!logoText) return;
logoLink.addEventListener('mouseenter', function() { const currentText = logoText.textContent; logoText.setAttribute('data-text', currentText || ''); logoText.textContent = hoverTitle; });
logoLink.addEventListener('mouseleave', function() { const originalText = logoText.getAttribute('data-text'); if (originalText) { logoText.textContent = originalText; } });}
initHoverTitle();document.addEventListener('astro:page-load', initHoverTitle);document.addEventListener('swup:contentReplaced', initHoverTitle);4.5 自定义方法
修改 hoverTitle 值即可自定义:
hoverTitle: "✨ 发现更多精彩内容!", // 趣味风格hoverTitle: "w(゚Д゚)w 不要走!", // 惊讶风格hoverTitle: "🎉 欢迎回来!", // 欢迎风格五、改进三:下拉菜单点击触发
5.1 背景
原实现使用 CSS :hover 触发下拉菜单,存在以下问题:
- 移动端/触屏设备无法使用
- 鼠标移出菜单区域时容易误关
- 与无障碍访问冲突
改为点击触发(.dropdown-open 类切换),与参考博客一致。
5.2 CSS 变更
src/styles/main.css — 全局样式修改:
/* 旧:hover 触发 */.dropdown-container:hover .dropdown-menu,.dropdown-container:focus-within .dropdown-menu { @apply opacity-100 visible pointer-events-auto translate-y-0;}
/* 新:click 触发 */.dropdown-container.dropdown-open .dropdown-menu,.dropdown-container:focus-within .dropdown-menu { @apply opacity-100 visible pointer-events-auto translate-y-0;}src/components/layout/DropdownMenu.astro — 组件内样式同步修改:
/* 新:点击触发 */.dropdown-container.dropdown-open .dropdown-menu { @apply opacity-100 visible pointer-events-auto translate-y-0;}
.dropdown-container.dropdown-open .dropdown-arrow { @apply rotate-180;}5.3 JavaScript 逻辑
function initDropdowns() { const dropdowns = document.querySelectorAll('[data-dropdown]');
dropdowns.forEach(dropdown => { const trigger = dropdown.querySelector('[data-dropdown-trigger]'); if (!trigger) return;
// 克隆节点清除旧事件监听器 const newTrigger = trigger.cloneNode(true); trigger.parentNode?.replaceChild(newTrigger, trigger);
// 点击触发器 → 切换 .dropdown-open 类 newTrigger.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); dropdown.classList.toggle('dropdown-open'); const isOpen = dropdown.classList.contains('dropdown-open'); newTrigger.setAttribute('aria-expanded', String(isOpen)); });
// Escape 键关闭 newTrigger.addEventListener('keydown', function(e) { if (e.key === 'Escape') { dropdown.classList.remove('dropdown-open'); newTrigger.setAttribute('aria-expanded', 'false'); newTrigger.focus(); } }); });}
// 点击外部关闭所有下拉菜单document.addEventListener('click', function(e) { const dropdowns = document.querySelectorAll('[data-dropdown]'); dropdowns.forEach(dropdown => { if (!dropdown.contains(e.target)) { dropdown.classList.remove('dropdown-open'); } });});5.4 交互行为
| 操作 | 行为 |
|---|---|
| 点击有子菜单的导航项 | 打开下拉菜单 |
| 再次点击 | 关闭下拉菜单 |
| 点击页面其他区域 | 关闭所有下拉菜单 |
| 按 Escape 键 | 关闭所有下拉菜单并聚焦回触发器 |
| 点击子菜单项 | 跳转链接并关闭菜单 |
六、改进四:Guestbook 入口统一
6.1 变更说明
改前:Guestbook 同时作为顶级链接 + “动态”子菜单项出现
主页 | 归档 | 网站导航 | 留言板 | 动态(说说/相册/留言板) | ...改后:仅保留在”动态”子菜单中
主页 | 分类 | 归档 | 网站导航 | 动态(说说/相册/留言板) | ...6.2 代码变更
在 src/config/navBarConfig.ts 中移除顶级链接:
// 根据配置决定是否添加留言板if (siteConfig.pages.guestbook) { links.push(LinkPreset.Guestbook);}“动态”子菜单中保持不变:
children: [ ...(siteConfig.pages.talks ? [LinkPreset.Talk] : []), ...(siteConfig.pages.gallery ? [LinkPreset.Gallery] : []), ...(siteConfig.pages.guestbook ? [LinkPreset.Guestbook] : []), // 保留],七、改进五:LinkPreset 枚举扩展
7.1 变更说明
为常用页面添加独立的 LinkPreset 枚举值,保持与参考博客一致:
export enum LinkPreset { Home = 0, Archive = 1, About = 2, Friends = 3, Sponsor = 4, Guestbook = 5, Bangumi = 6, Gallery = 7, Talk = 8, Categories = 9, // ← 新增 Books = 10, // ← 新增 MoviesGames = 11, // ← 新增 MusicPage = 12, // ← 新增 Changelog = 13, // ← 新增}7.2 预设映射
在 src/constants/link-presets.ts 中添加映射:
[LinkPreset.Categories]: { name: i18n(I18nKey.categories), url: "/categories/", icon: "material-symbols:folder-open",},[LinkPreset.Books]: { name: "书架", url: "/books/", icon: "material-symbols:book-5",},[LinkPreset.MoviesGames]: { name: "影视与游戏", url: "/movies-games/", icon: "material-symbols:movie",},[LinkPreset.MusicPage]: { name: i18n(I18nKey.music), url: "/music/", icon: "material-symbols:music-note",},[LinkPreset.Changelog]: { name: "更新日志", url: "/changelog/", icon: "material-symbols:history",},7.3 使用方式
扩展后的枚举可在 navBarConfig.ts 中直接引用:
// 使用预设(推荐)links.push(LinkPreset.Books);
// 也可继续使用自定义 NavBarLinklinks.push({ name: "书架", url: "/books/", icon: "material-symbols:book-5",});八、导航栏最终结构
Navbar.astro ← 主容器├── Logo 区域 (hoverTitle 支持)│ ├── Icon / Picture / Image│ └── 标题文字 (mouseenter 变化)├── 桌面端导航链接 (.hidden.lg:flex)│ ├── 主页 → /│ ├── 分类 → /categories/ ← 新增│ ├── 归档 → /archive/│ ├── 网站导航 → /projects/│ ├── 动态 (点击触发下拉) ← 改为 click│ │ ├── 说说│ │ ├── 相册│ │ └── 留言板│ ├── 记录 (点击触发下拉)│ │ ├── 书架│ │ ├── 影视与游戏│ │ ├── 音乐│ │ ├── 更新日志│ │ ├── 规划│ │ └── 足迹│ └── 关于 (点击触发下拉)│ ├── 关于我│ ├── 友链│ └── 赞助├── 右侧工具栏│ ├── 搜索 (Search.svelte)│ ├── 音乐按钮│ ├── 管理后台│ ├── 显示设置│ ├── 亮暗切换│ └── 汉堡菜单 (移动端)├── 移动端侧滑菜单 (NavMenuPanel.astro)└── 显示设置面板九、文件变更清单
| 文件 | 操作 | 说明 |
|---|---|---|
src/types/config.ts | 修改 | LinkPreset 新增 5 个枚举值 + navbar.hoverTitle 类型 |
src/constants/link-presets.ts | 修改 | 新增 5 个预设映射 |
src/config/siteConfig.ts | 修改 | 新增 hoverTitle 配置 |
src/config/navBarConfig.ts | 修改 | 添加分类链接 + 移除顶级 Guestbook |
src/components/layout/Navbar.astro | 修改 | hoverTitle data 属性 + 事件脚本 |
src/components/layout/DropdownMenu.astro | 修改 | CSS hover→click + 脚本重写 |
src/styles/main.css | 修改 | 全局下拉菜单 CSS 改为 click 触发 |
src/pages/categories.astro | 新建 | 分类独立页面 |
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!