给博客侧边栏添加一个可折叠的好友模块

从零开始,一步步在 Nuxt 博客的侧边栏中添加一个可折叠展开的好友列表模块,包含组件编写、样式设计、数据配置和集成上线的完整过程。

给博客侧边栏添加一个可折叠的好友模块

最近给自己的博客侧边栏加了一个"好友"模块,默认折叠,点开就能看到好友列表,每个好友都能直接跳转到对方的网站。这篇文章把整个制作过程记录下来,方便有同样需求的朋友参考。

准备工作

在动手之前,先确认一下你手头的东西:

  • 一个基于 Nuxt 3 的博客项目(本文以 Clarity 主题为例)
  • 装好了 Node.js 和 pnpm
  • 一个顺手的代码编辑器(VS Code 就行)
  • 对 Vue 3 组件有最基本的了解(知道 script setuptemplatestyle 是什么)

不需要你是 Vue 高手,只要能看懂基本的模板语法就够用了。

整体思路

先想清楚这个模块要做什么:

  1. 在侧边栏导航下方显示一个"好友"标题行
  2. 默认是折叠的,只显示标题和一个展开箭头
  3. 点击标题行可以展开/折叠好友列表
  4. 每个好友显示头像、名称和简短描述
  5. 点击好友条目在新标签页打开对方的网站
  6. 鼠标悬停时有视觉反馈

拆成三个部分来做:

  • 组件文件:负责展示和交互逻辑
  • 数据配置:好友列表的数据源
  • 集成接入:把组件放进侧边栏

第一步:创建好友组件

app/components/blog/ 目录下新建 BlogFriends.vue 文件。Nuxt 会自动根据目录结构注册组件,所以放在 blog/ 目录下就能以 <BlogFriends> 的方式使用。

编写模板结构

先搭好 HTML 骨架:

vue
<template>
<div class="blog-friends">
  <!-- 标题行,点击切换展开/折叠 -->
  <button class="friends-header" @click="isExpanded = !isExpanded">
    <Icon name="tabler:users" />
    <span class="nav-text">好友</span>
    <Icon class="toggle-icon" :class="{ expand: isExpanded }" name="tabler:chevron-down" />
  </button>

  <!-- 好友列表,用 Transition 做动画 -->
  <Transition name="collapse">
    <div v-show="isExpanded" class="friends-content">
      <a
        v-for="friend in list"
        :key="friend.name"
        :href="friend.url"
        target="_blank"
        rel="noopener"
        class="friend-item"
      >
        <NuxtImg v-if="friend.avatar" :src="friend.avatar" :alt="friend.name" class="friend-avatar" loading="lazy" />
        <Icon v-else name="tabler:user" class="friend-avatar-icon" />
        <div class="friend-info">
          <span class="friend-name">{{ friend.name }}</span>
          <span v-if="friend.desc" class="friend-desc">{{ friend.desc }}</span>
        </div>
      </a>
    </div>
  </Transition>
</div>
</template>

几个要点说明:

  • 标题行用 <button>:保证键盘可访问,屏幕阅读器也能正确识别
  • v-show 而非 v-if:配合 <Transition> 做展开/折叠动画时,v-show 更合适,因为组件始终在 DOM 中,动画才能平滑过渡
  • target="_blank":好友链接在新标签页打开,不打断当前浏览
  • NuxtImg + loading="lazy":头像懒加载,不拖慢首屏速度
  • 没有头像时显示默认图标:用 v-if/v-else 做兜底

编写脚本逻辑

脚本部分很简单,只需要定义数据和状态:

vue
<script setup lang="ts">
interface FriendItem {
  name: string
  url: string
  avatar?: string
  desc?: string
}

const props = defineProps<{
  list?: FriendItem[]
}>()

const isExpanded = ref(false)
</script>
  • FriendItem 接口:定义好友数据的类型,avatardesc 是可选的
  • list 属性:从外部传入好友列表数据
  • isExpanded:控制展开/折叠状态,默认 false(折叠)

编写样式

样式要和侧边栏现有的导航项保持一致,关键是复用已有的 CSS 变量:

这里有几个设计决策值得提一下:

  • font: inheritcolor: inherit:让按钮的字体和颜色继承父元素,和侧边栏其他导航项保持一致
  • var(--c-bg-soft):使用项目定义的 CSS 变量,自动适配浅色/深色主题
  • 箭头旋转动画:展开时箭头旋转 180 度,给用户明确的视觉反馈

好友条目的样式:

  • 头像用 border-radius: 50% 做圆形:和友链页面的风格统一
  • 文字溢出省略号:名称和描述都可能很长,用 text-overflow: ellipsis 处理
  • 描述用更小字号 + 半透明:层次分明,不抢名称的视觉权重

折叠动画

展开/折叠的过渡动画:

scss
.collapse-enter-active,
.collapse-leave-active {
  transition: all 0.25s ease;
}

.collapse-enter-from,
.collapse-leave-to {
  opacity: 0;
  max-height: 0;
}

.collapse-enter-to,
.collapse-leave-from {
  opacity: 1;
  max-height: 500px;
}

这里同时用了 opacitymax-height 两个属性做过渡。max-height 从 0 到 500px 模拟高度变化,opacity 做淡入淡出,两者配合效果比较自然。

第二步:添加好友数据配置

数据放在 app.config.ts 中,这样运行时通过 useAppConfig() 就能读取,修改也不需要重新构建。

app.config.ts 中添加 friends 字段:

ts
friends: [
  { name: 'GuuGuai', url: 'https://blog.guuguai.site/', avatar: 'https://cdn.libravatar.org/avatar/646331bff8f19a0e05679c3cc0fc54d6?s=160', desc: '古怪杂记本' },
  { name: '小李同学', url: 'https://blog.junjieli.top/', avatar: 'https://www.junjieli.top/logo_64x64.png', desc: '一支努力变强的小彩笔' },
  // 添加更多好友...
] satisfies { name: string, url: string, avatar?: string, desc?: string }[],

satisfies 而不是 as 来做类型约束,这样既能保证数据格式正确,又不会丢失类型推断。

每个好友条目只需要填:

字段是否必填说明
name必填好友名称
url必填好友网站链接
avatar可选头像图片地址,不填则显示默认图标
desc可选简短描述,不填则不显示描述行

第三步:集成到侧边栏

打开 app/components/blog/BlogSidebar.vue,在导航列表的 </template> 之后、</nav> 之前加入一行:

vue
<BlogFriends :list="appConfig.friends" />

就这么简单。因为 Nuxt 会自动注册 blog/ 目录下的组件,所以不需要手动 import。

完整的侧边栏结构变成了:

vue
<nav class="sidebar-nav scrollcheck-y">
  <!-- 搜索按钮 -->
  <div class="search-btn ...">...</div>

  <!-- 原有导航项 -->
  <template v-for="...">...</template>

  <!-- 新增:好友模块 -->
  <BlogFriends :list="appConfig.friends" />
</nav>

注意事项

头像图片的选择

头像 URL 尽量用稳定的图床。推荐几个方案:

  • Gravatar / Cravatar:根据邮箱哈希生成头像,比较通用
  • GitHub 头像https://avatars.githubusercontent.com/用户名,GitHub 用户的首选
  • QQ 头像:项目中已有 getOicqAvatar() 工具函数可以直接用

避免使用对方网站根目录下的 favicon 作为头像,那种图片通常分辨率太低,显示效果不好。

数据量控制

侧边栏空间有限,好友条目建议控制在 5-10 个。如果好友很多,可以只放最常互动的几个,其余的引导到友链页面查看。

主题适配

样式里全部使用了项目定义的 CSS 变量(如 var(--c-bg-soft)var(--c-text-2)),所以浅色和深色主题都能自动适配,不需要额外处理。

常见问题

组件没有显示出来

检查以下几点:

  1. 文件是否放在了 app/components/blog/ 目录下,文件名是否为 BlogFriends.vue
  2. app.config.ts 中的 friends 数组是否有数据
  3. 侧边栏模板中是否正确添加了 <BlogFriends :list="appConfig.friends" />

头像加载失败显示空白

NuxtImg 在图片加载失败时不会显示占位符。可以给好友条目加一个 CSS 背景色作为兜底:

scss
.friend-avatar {
  background-color: var(--c-bg-soft);
}

这样即使图片加载失败,也会显示一个带背景色的圆形区域,不至于完全空白。

折叠动画不流畅

max-height 过渡的一个已知问题是:如果实际内容高度远小于设定的 max-height 值,收起动画会有延迟感。如果好友数量较少,可以把 max-height500px 调小到 300px 左右,动画会更紧凑。

移动端显示异常

侧边栏在移动端是以抽屉形式弹出的,组件使用了 scoped 样式,不会和外部样式冲突。如果发现布局问题,检查一下 .friends-contentpadding-inline-start 是否在窄屏下过大,可以加一个媒体查询调整:

scss
@media (max-width: $breakpoint-mobile) {
  .friends-content {
    padding-inline-start: 0.5em;
  }
}

总结

整个模块的搭建过程其实就是三步:写组件 → 配数据 → 接进去。核心工作量在组件的模板和样式上,逻辑部分非常轻量。关键是复用项目已有的 CSS 变量和图标体系,这样不用写很多代码就能和现有风格保持一致。

新故事即将发生
青岑re类begin题wp

评论区

评论加载中...