Skip to content
0

友链新风格

时光-友链新风格

image-20250903062119413

目录

[toc]

背景

Teek默认的友链风格 不太好看,然时光大佬提供了一个不错的解决方案,感谢大佬。❤️❤️❤️

环境

Teek@1.4.6-2025.8.31上测试成功。

2025年9月23日测试。

版权

配置来自《时光驿站》大佬的《友链组件》文章,感谢大佬💖💖💖。

配置

提示

特别中意唯知笔记的友链组件,作者也出了教程,但配置相对复杂,需要额外安装第三方库,不是我想要的,所以仿照作者的友链界面和实现流程,重新编写了这个组件,相较于原组件:

  • 无需依赖第三方变量,减少配置成本;
  • 用原生 Flex 布局替代 Element Plus 的 el-row/el-col,无需额外安装 UI 库;
  • 具有相同的核心功能(分组展示、自适应布局、hover 交互),同时支持自定义隐藏评论区。

1.新建组件

  1. 新建组件与文件结构

    在 VitePress 主题目录下创建友链组件相关文件,结构如下:

    ts
    .vitepress
    ├─ theme
    │  ├─ components
    │  │  ├─ SLink
    │  │  │  ├─ index.vue
    │  │  │  └─ LinkItem.vue
    │  │  ├─ MyLayout.vue
    │  │  ├─ Twikoo.vue
    │  └─ index.ts
    └─ config.mts

    image-20250903060120881

  2. 编写友链组件代码

    友链组件分为两部分:index.vue(负责分组展示与留言区)和 LinkItem.vue(单个友链卡片),完整代码如下:

    详细信息
    vue
    <template>
      <div class="my-links-container">
        <!-- 页面主标题区域 -->
        <div class="my-links-title">
          <h1>{{ title }}</h1>
        </div>
    
        <!-- 友链分组列表,每个分组包含标题、描述和友链列表 -->
        <div v-for="(group, index) in linksData" :key="index" class="my-links-group">
          <!-- 分组标题容器 -->
          <div class="title-wrapper">
            <h3>{{ group.title }}</h3>
          </div>
    
          <!-- 分组描述文本 -->
          <p class="group-desc">{{ group.desc }}</p>
    
          <!-- 友链列表容器 -->
          <div class="links-grid">
            <!-- 每个友链项使用LinkItem子组件展示,通过:data传递友链信息 -->
            <div v-for="link in group.list" :key="link.link" class="links-grid__item">
              <LinkItem :data="link" />
            </div>
          </div>
        </div>
    
        <!-- 留言/评论区域,默认显示,可通过frontmatter隐藏 -->
        <div v-if="shouldShow" class="my-message-section">
          <div class="title-wrapper">
            <h3>留链吗</h3>
          </div>
          <p>留恋的小伙伴,想要和我做友链 💞</p>
    
          <!-- 留言卡片容器 -->
          <div class="message-card">
            <p>欢迎在评论区留言,格式如下:</p>
            <!-- 示例格式 -->
            <pre>
    名称: One
    链接: https://onedayxyy.cn/
    头像: https://img.onedayxyy.cn/images/Teekwebsite/xyy-logo.webp
    站点截图:https://img.onedayxyy.cn/images/image-20250502073710566.png
    描述: 明心静性,爱自己</pre>
            <!-- 评论区插槽 -->
            <!-- 默认为Twikoo评论组件,可通过插槽自定义其他评论系统 -->
            <slot name="comments">
              <Twikoo />
            </slot>
          </div>
        </div>
      </div>
    </template>
    
    <script setup>
    import { useData } from "vitepress";
    import LinkItem from "./LinkItem.vue";
    // 导入Twikoo评论组件
    //import Twikoo from "../Twikoo.vue";
    import { computed } from 'vue'
    
    /**
     * 单个友链的数据结构定义
     * @typedef {Object} Link
     * @property {string} name - 友链网站名称
     * @property {string} link - 友链网站URL地址
     * @property {string} avatar - 友链网站头像/Logo的图片URL
     * @property {string} descr - 友链网站的简短描述
     * @property {boolean} [irregular] - 可选参数,默认值为false,为false时将把头像处理为圆形
     */
    
    /**
     * 友链分组的数据结构定义
     * @typedef {Object} LinkGroup
     * @property {string} title - 分组标题
     * @property {string} desc - 分组描述文字
     * @property {Link[]} list - 该分组下的友链列表数组
     */
    
    // 从页面frontmatter中获取配置数据
    const { frontmatter } = useData();
    
    // 从frontmatter中读取links字段,如果未定义则使用空数组
    const linksData = computed(() => frontmatter.value.links || []);
    
    // 从frontmatter中读取title字段,默认值为"我的友链"
    const title = computed(() => frontmatter.value.title || '我的友链');
    
    // 当frontmatter中comments为false时隐藏,默认显示
    const shouldShow = computed(() => frontmatter.value.comments !== false);
    </script>
    
    <style scoped>
    /* 主容器样式 */
    .my-links-container {
      max-width: 1500px;
      margin: 0 auto;
      padding: 40px 10px;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
        Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
    }
    
    /* 标题区域样式 */
    .my-links-title {
      margin-bottom: 50px;
      padding: 0 10px;
    }
    
    /* 主标题样式 */
    .my-links-title h1 {
      font-size: 2rem;
      font-weight: 600;
      background: -webkit-linear-gradient(10deg, #bd34fe 5%, #e43498 15%);
      background-clip: text;
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      -webkit-font-smoothing: antialiased;
      text-rendering: optimizeLegibility;
      line-height: 1.2;
      display: inline-block;
    }
    
    /* 分组标题装饰线样式 */
    .title-wrapper {
      position: relative;
      margin: 40px 0;
      height: 1px;
      background: #ddd;
      transition: 0.3s;
    }
    
    /* 分组标题文字样式 */
    .title-wrapper h3 {
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      background: var(--vp-c-bg);
      padding: 0 20px;
      font-size: 1.3rem;
      font-weight: 600;
      color: var(--vp-c-text-1);
      z-index: 1;
    }
    
    /* 分组描述文字样式 */
    .group-desc {
      text-align: center;
      color: var(--vp-c-text-2);
      font-size: 0.95rem;
      margin-bottom: 30px;
      padding: 0 10px;
    }
    
    /* 友链网格布局 - 核心响应式实现 */
    .links-grid {
      display: flex;
      flex-wrap: wrap;
      justify-content: center; /* 让所有行的内容居中对齐 */
      gap: 24px;
      margin-bottom: 60px;
      padding: 0 8px;
    }
    
    /* 每个友链项的样式,设置基础宽度 */
    .links-grid__item {
      flex: 0 0 calc(100% - 24px); /* 移动设备:每行1个 */
      break-inside: avoid;
    }
    
    /* 平板设备:每行2个 */
    @media (min-width: 768px) {
      .links-grid__item {
        flex: 0 0 calc(50% - 24px);
      }
    }
    
    /* 桌面设备:每行最多4个 */
    @media (min-width: 1024px) {
      .links-grid__item {
        flex: 0 0 calc(25% - 24px);
      }
    }
    
    /* 留言区样式 */
    .my-message-section {
      text-align: center;
      margin-top: 20px;
    }
    
    /* 留言卡片样式 */
    .message-card {
      width: 100%;
      max-width: 1500px;
      margin: 30px auto;
      padding: 32px;
      border-radius: 12px;
      background: var(--vp-c-bg);
      box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
      border: 1px solid var(--vp-c-divider);
      text-align: left;
      transition: all 0.2s ease;
    }
    
    /* 移动端留言卡片适配 */
    @media (max-width: 768px) {
      .message-card {
        padding: 24px;
        margin: 24px auto;
      }
    }
    
    /* 示例格式代码块样式 */
    .message-card pre {
      background: var(--vp-code-block-bg);
      padding: 16px;
      border-radius: 8px;
      font-size: 0.95rem;
      overflow-x: auto;
      margin: 20px 0;
      border: 1px solid var(--vp-c-divider);
      line-height: 1.5;
    }
    
    /* 留言卡片悬停效果 */
    .message-card:hover {
      transform: translateY(-2px);
      box-shadow: 0 8px 28px rgba(0, 0, 0, 0.12);
    }
    </style>
    vue
    <template>
      <div class="link-item-card">
        <!-- 标签 -->
        <div class="link-tag" v-if="data.tag">{{data.tag}}</div>
        <a :href="data.link" :title="data.name" target="_blank" rel="noopener">
          <!-- 头像 -->
          <div class="link-avatar">
            <img
              v-if="!imageFailed && data.avatar"
              :src="data.avatar"
              :alt="data.name"
              @error="handleImageError"
              :class="{ irregular: data.irregular }"
            />
            <span v-else class="avatar-placeholder">
              {{ data.name ? data.name.charAt(0).toUpperCase() : '?' }}
            </span>
          </div>
    
          <!-- 信息 -->
          <div class="link-content">
            <div class="link-name">{{ data.name }}</div>
            <div class="link-desc" :title="data.descr">
              {{ data.descr }}
            </div>
          </div>
        </a>
      </div>
    </template>
    
    <script setup>
    defineProps({
      data: {
        type: Object,
        required: true
      }
    })
    
    import { ref } from 'vue'
    const imageFailed = ref(false)
    
    const handleImageError = () => {
      imageFailed.value = true
    }
    </script>
    
    <style scoped>
    .link-tag {
      position: absolute;
      top: 8px;
      right: 8px;
      z-index: 10;
      font-size: 0.75rem;
      font-weight: 500;
      padding: 1px 6px;
      border-radius: 8px;
      background-color: var(--vp-badge-tip-bg);
      color: var(--vp-badge-tip-text);
      letter-spacing: 0.2px;
    }
    
    .link-item-card {
      position: relative;
      height: 100px;
      border-radius: 12px;
      background: var(--vp-c-bg);
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
      border: 1px solid var(--vp-c-divider);
      transition: all 0.3s ease;
      overflow: hidden;
    }
    
    .link-item-card:hover {
      transform: translateY(-4px);
      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
    }
    
    .link-item-card a {
      display: flex;
      align-items: center;
      height: 100%;
      text-decoration: none;
      color: inherit;
    }
    
    .link-avatar {
      flex: 0 0 100px;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: transform 0.3s ease;
    }
    
    .link-avatar img {
      width: 60px;
      height: 60px;
      border-radius: 50%;
      object-fit: cover;
      transition: transform 0.3s ease;
    }
    
    .link-avatar img.irregular {
      border-radius: 8px;
      object-fit: contain;
    }
    
    .link-avatar .avatar-placeholder {
      width: 60px;
      height: 60px;
      background: #f0f0f0;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-weight: 600;
      color: #555;
      font-size: 1.2rem;
    }
    
    .link-item-card:hover .link-avatar img,
    .link-item-card:hover .avatar-placeholder {
      transform: scale(1.2);
    }
    
    .link-content {
      flex: 1;
      padding: 0 16px 0 0px;
    }
    
    .link-name {
      font-size: 1rem;
      font-weight: 600;
      color: var(--vp-c-text-1);
      margin-bottom: 6px;
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 2;
      line-clamp: 2;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: normal;
      word-wrap: break-word;
    }
    
    .link-desc {
      font-size: 0.875rem;
      color: var(--vp-c-text-2);
      display: -webkit-box; /* 兼容 WebKit 旧版本 */
      display: box;
      -webkit-box-orient: vertical;
      box-orient: vertical;
      -webkit-line-clamp: 2; /* 兼容旧版 */
      line-clamp: 2; /* 标准属性(核心) */
      overflow: hidden;
      line-height: 1.4;
    }
    </style>

2.注册组件

ts
import SLink from "./components/SLink/index.vue";

export default {
  enhanceApp({ app }) {
    // 注册全局组件
    app.component("friend-link", SLink);
  },
};

3.使用方法

  • frontmatter 中设置 layoutfriend-link,启用友链布局模式
  • 建议设置 sidebar: false,界面更美观
  • 设置 comments: false 可关闭底部评论区

新建如下文件:

使用模板
md
---
date: 2025-09-03 02:06:02
layout: friend-link
title: 我的友链
sidebar: false
permalink: /about/friend-links
article: false
comment: false
copyright: false
articleShare: false
appreciation: false
links:
  - title: 个人站点
    #desc: "建站中学习和使用了以下博客/网站的技术和分享,特别鸣谢!💖"
    list:
      - name: One
        link: https://onedayxyy.cn/
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/xyy-logo.webp
        irregular: true
        descr: 明心静性,爱自己(基于vitepress)
      - name: One
        link: https://zola.onedayxyy.cn/
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/xyy-logo.webp
        irregular: true
        descr: 明心静性,爱自己(基于rust开发的zola,移植自张洪heo博客)         
      # - name: VitePress
      #   link: https://vitepress.dev/zh/
      #   avatar: https://vitepress.dev/vitepress-logo-mini.svg
      #   irregular: true
      #   descr: 由 Vite 和 Vue 驱动的静态站点生成器
  - title: VitePress
    # desc: "建站中学习和使用了以下博客/网站的技术和分享,特别鸣谢!💖"
    list:
      - name: vitepress-theme-teek
        link: https://vp.teek.top/
        avatar: https://vitepress.yiov.top/logo.png
        irregular: true
        descr: 一个轻量、简洁高效、灵活配置,易于扩展的 VitePress 主题
        tag: Teek官网
      - name: VitePress
        link: https://vitepress.dev/zh/
        avatar: https://vitepress.dev/vitepress-logo-mini.svg
        irregular: true
        descr: 由 Vite 和 Vue 驱动的静态站点生成器
  - title: Teek道友
    # desc: "聚集众多优秀独立博客,随机传送 \U0001F680"
    list:
      - name: 天客 - Teeker
        descr: 朝圣的使徒,正在走向编程的至高殿堂!(Teek作者,大佬666)
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/notes.teek.top.png
        link: https://notes.teek.top/  
        tag: Teek作者
        irregular: true
      - name: One 
        descr: 明心静性,爱自己 
        avatar: https://img.onedayxyy.cn/images/xyy-logo.ico 
        link: https://onedayxyy.cn/ 
        irregular: true
      - name: Hyde Blog
        descr: 前端大佬,666 
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/hyde.webp
        link: https://teek.seasir.top/
        badge: 前端大佬
        irregular: true
      - name: 二丫讲梵 
        descr: 💻学习📝记录🔗分享 
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/eryajiangfan.png 
        link: https://jenkinsguide.opsre.top/ 
        # link: https://wiki.eryajf.net/ 
        irregular: true
      - name: 威威 Blog
        descr: teek伙伴
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/dl-web.top.png
        link: https://dl-web.top/
        irregular: true
        tag: Teek开发者2号
      - name: W3C
        descr: teek伙伴
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/teek.w3c.cool.svg
        link: https://teek.w3c.cool/
        irregular: true         
      - name: 时光驿站
        descr: 干活满满的技术笔记
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/kandu.cxcare.top.svg
        link: https://kandu.cxcare.top/ 
        irregular: true               
      - name: 凿壁偷光不算偷 Blog
        descr: teek道友 
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/sinc-us-kg-favicon.ico 
        link: https://sinc.us.kg/  
        irregular: true
      - name: SnowLin Blog
        descr: 喵喵(? 
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/SnowLinBlog-logo.png 
        link: https://blog.snowlinlan.com/
        irregular: true
      - name: 心流笔记
        descr: 坚持 & 汲取 & 分享(一个记录生活与学习过程中灵感和感悟的空间)
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/blog.wilsonzy.cn.ico
        link: http://blog.wilsonzy.cn/c/StreamNotes/
        irregular: true
      - name: 科技语者
        descr: 拥抱科技奔赴星辰大海
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/www.uopy.top.ico
        link: https://www.uopy.top/
        irregular: true
      - name: OpForge
        descr: 运维锻造,知识沉淀
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/opforge.srebro.cn.png
        link: https://opforge.srebro.cn/
        irregular: true
      - name: 乔克视界
        descr: 云原生爱好者
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/jokerbai.com.ico
        link: https://jokerbai.com/
        irregular: true
      - name: 记得勇敢
        descr: 常用组件及代码封装
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/vp.xiaoying.org.cn.png
        link: https://vp.xiaoying.org.cn/
        irregular: true
      - name: 骏骏骏的博客
        descr: 后端程序员
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/blog.azhxsn.cn.svg
        link: https://blog.azhxsn.cn/
        irregular: true
      - name: MaraPython
        descr: 行而不辍,未来可期
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/marapython.com.png
        link: https://marapython.com/
        irregular: true
      - name: Mujin Blog
        descr: 只要一息尚存,就还有希望
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/blog.charily.top.ico
        link: https://blog.charily.top/
        irregular: true
      - name: 白木
        descr: SCUM中文服务器后台说明文档
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/st.samgame.cn.png
        link: https://st.samgame.cn/
        irregular: true                  
  - title: 漂亮的博客
    # desc: "聚集众多优秀独立博客,随机传送 \U0001F680"
    list:
      - name: 王嘉祥
        link: https://blog.jiaxiang.wang/
        avatar: https://img.onedayxyy.cn/images/Teek/Teekwebsite/blog.jiaxiang.wang.webp
        descr: 唱响科普和人生兴事,分享科技与美好生活(rust写的zola主题,移植于张洪heo)
        irregular: true        
categories:
  - 关于
coverImg: https://img.onedayxyy.cn/images/TeekCover/1.webp
---

4.组件效果

http://localhost:5173/about/friend-links

image-20250923065657024

结束。

最近更新