WARNING

在开始使用实践之前,你需要注意,自建网站需要一定的相关知识,你可能会遇到一系列的问题,请确保你有独立解决问题的能力。
假如你想修改主题的某些组件或样式,你还需要掌握 Vue.js 的相关知识
虽然如今你可以借助 AI 来辅助完成一些需求,但知其然,知其所以然
是每一个优秀的人都应该具备的基本素养。

需要 DailyHotApi 相关api

在项目根目录创建一个 .env 文件

写入以下内容:

VITE_HELLOGITHUB_API_URL="https://api地址/hellogithub"

新建 HelloGithubHot.vue 文件

新建在:.vitepress\theme\components\Aside\Widgets\HelloGithubHot.vue
写入以下内容:

vue
<template>
  <!-- 移动端不渲染 -->
  <div v-if="!isMobile" class="site-data s-card">
    <div class="title">
      <i class="iconfont icon-chart"></i>
      <span class="title-name">HelloGithub 热榜</span>
    </div>

    <!-- 列表容器:动态计算高度,始终只露出前 5 条 -->
    <div class="all-data" ref="listContainer" :style="{ maxHeight: containerHeight + 'px' }">
      <div class="data-item" v-for="(item, index) in list" :key="item.id">
        <span class="name">{{ index + 1 }}.</span>
        <span class="num">
          <a :href="item.url" target="_blank" rel="noopener">
            {{ item.title }}
          </a>
        </span>
      </div>
    </div>

    <!-- 最后更新时间 -->
    <div v-if="formattedUpdateTime" class="update-time">最后更新:{{ formattedUpdateTime }}</div>
  </div>
</template>

<script setup>
// 数据与状态
// —— 新增:定义 emit ——
const emit = defineEmits(["fetch-error"]);

const list = ref([]);
const rawUpdateTime = ref("");
const isMobile = ref(false);
const containerHeight = ref(0);
const listContainer = ref(null);
// 从环境变量读取 API 地址
const API_URL = import.meta.env.VITE_HELLOGITHUB_API_URL;

// 将 ISO 时间解析成 'YYYY-MM-DD HH:mm:ss'
const formattedUpdateTime = computed(() => {
  if (!rawUpdateTime.value) return "";
  const d = new Date(rawUpdateTime.value);
  const pad = (n) => String(n).padStart(2, "0");
  return (
    `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +
    ` ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
  );
});

// 简单 UA 判断移动端
function detectMobile() {
  return /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}

async function fetchData() {
  try {
    const res = await fetch(API_URL);
    const data = await res.json();
    list.value = data.data || [];
    rawUpdateTime.value = data.updateTime || "";
    await nextTick();
    calcHeight();
  } catch (e) {
    console.error("HelloGithub 热榜加载失败", e);
    // —— 关键:向父组件抛出错误 ——
    emit("fetch-error", e);
  }
}

// 计算前 5 条总高度(包含 margin)
function calcHeight() {
  const container = listContainer.value;
  if (!container) return;
  const items = Array.from(container.querySelectorAll(".data-item")).slice(0, 5);
  const total = items.reduce((sum, el) => {
    const style = getComputedStyle(el);
    const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom);
    return sum + el.getBoundingClientRect().height + margin;
  }, 0);
  containerHeight.value = total;
}

onMounted(() => {
  isMobile.value = detectMobile();
  if (isMobile.value || !API_URL) return;
  fetchData();
  window.addEventListener("resize", calcHeight);
});
</script>

<style lang="scss" scoped>
.site-data {
  margin-bottom: 1rem;
  .iconfont {
    opacity: 0.6;
    margin-right: 6px;
  }
  .title-name {
    opacity: 0.8;
  }
  .title {
    padding: 0.3rem 0.5rem;
    margin-bottom: 5px;
    font-weight: bold;
    display: flex;
    align-items: center;
    opacity: 0.75;
  }
  .all-data {
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;

    .data-item {
      display: flex;
      align-items: flex-start;
      padding: 0.2rem 0.1rem;
      line-height: 1.4rem;
      font-size: 14px;

      .name {
        flex-shrink: 0;
        width: 1.2rem;
        text-align: center;
        opacity: 0.8;
        font-weight: bold;
      }
      .num {
        flex: 1;
        margin-left: 0.3rem;
        a {
          display: -webkit-box;
          -webkit-box-orient: vertical;
          -webkit-line-clamp: 2;
          line-clamp: 2;
          overflow: hidden;
          text-overflow: ellipsis;
          word-break: break-word;
          white-space: normal;
          opacity: 0.8;
          text-decoration: none;
        }
      }

      &:last-child {
        padding-bottom: 0;
      }
    }
  }

  /* 更新时间样式 */
  .update-time {
    margin: 0.2rem 0.5rem;
    font-size: 12px;
    color: #888;
    text-align: right;
  }
}
</style>

修改 index.vue

修改index.vue使Weather.vue挂载在侧边
index.vue路径:
.vitepress\theme\components\Aside\index.vue

vue
<template>
  <aside class="main-aside">
    <Hello v-if="theme.aside.hello.enable" class="weidgets" />
    <div class="sticky">
      <Toc v-if="theme.aside.toc.enable && showToc" class="weidgets" />
  <Weather
    v-if="theme.aside.weather.enable && showWeather"
    class="weidgets"
    @fetch-error="onWeatherError"
  />
      <Countdown class="weidgets" />
      <!-- HelloGithub 热榜 -->
    <HelloGithubHot
      v-if="theme.aside.HelloGithub.enable && showHot"
      @fetch-error="onHotError"
    />
      <Tags v-if="theme.aside.tags.enable" class="weidgets" />
      <SiteData v-if="theme.aside.siteData.enable" class="weidgets" />
    </div>
  </aside>
</template>
vue
// 已有:天气组件的显示开关
const showWeather = ref(true)
// 新增:热榜组件的显示开关
const showHot = ref(true)
// 一旦收到子组件的 fetch-error 事件,就把 showWeather 置为 false
function onWeatherError(err) {
  console.error('天气组件获取失败:', err)
  showWeather.value = false
}

function onHotError(err) {
  console.error('HelloGithub 热榜获取失败:', err)
  showHot.value = false
}

修改 themeConfig.mjs

.vitepress\theme\assets\themeConfig.mjs

js
HelloGithub: {
  enable: true,
},

完成

按照步骤做完后,你现在dev之后应该就可以看见一个热榜出现在右边了。祝愉快~

赞赏博主
评论 隐私政策