WARNING
在开始使用实践之前,你需要注意,自建网站需要一定的相关知识,你可能会遇到一系列的问题,请确保你有独立解决问题的能力。
假如你想修改主题的某些组件或样式,你还需要掌握 Vue.js 的相关知识
虽然如今你可以借助 AI 来辅助完成一些需求,但知其然,知其所以然
是每一个优秀的人都应该具备的基本素养。
需要
DailyHotApi
相关api
- 前往 GitHub 部署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之后应该就可以看见一个热榜出现在右边了。祝愉快~
给你的 Curve 主题添加一个热榜侧边栏https://chiyu.it/posts/2025/0725
赞赏博主
评论 隐私政策