前言
在逛博客友链时发现了一个好看的个人首页,于是进行了修改。
修改
自动获取文章
这里是在articles_list.ts里获取的,由于halo提供了RESTful API,所以我写了一个js脚本来获取数据。
import axios from 'axios';
import dotenv from 'dotenv';
import fs from 'fs';
// 加载环境变量
dotenv.config();
// 从环境变量中获取基础 URL、API Token 和每类文章的最大数量
const baseUrl = process.env.BASE_URL;
const apiToken = process.env.API_TOKEN;
const maxArticlesPerCategory = parseInt(process.env.MAX_ARTICLES_PER_CATEGORY) || 10; // 默认最大数量为 10
// 设置请求头,包含 API Token
const headers = {
Authorization: `Bearer ${apiToken}`,
};
/**
* 异步获取文章帖子数据
* 此函数从 API 获取文章数据,处理并分类,然后写入文件系统
*/
async function fetchPosts() {
try {
// 发起 GET 请求获取数据
const response = await axios.get(baseUrl, { headers });
// 检查响应状态
if (response.status === 200) {
// 解析基础域名
const baseDomain = new URL(baseUrl).origin;
// 获取响应数据中的项目
const items = response.data.items;
// 初始化数据,存储“最新”
const categoriesData = {
latest: {
title: "最新",
url: `${baseDomain}/archives`,
articles: [],
},
};
// 如果项目存在且为数组,则进行处理
if (items && Array.isArray(items)) {
// 按发布日期降序排序项目
items.sort((a, b) => new Date(b.post.spec.publishTime) - new Date(a.post.spec.publishTime));
// 遍历每个项目
items.forEach(item => {
const post = item.post.spec;
const postTitle = post.title;
const postUrl = item.post.status.permalink;
const postTime = post.publishTime;
const categories = item.categories.map(category => category.spec.displayName);
// 将时间格式修改为年月日
const formattedDate = new Date(postTime).toISOString().split('T')[0].replace(/-/g, '/');
const fullPostUrl = `${baseDomain}${postUrl}`;
// 向“最新”分类中添加文章,直到达到最大数量
if (categoriesData.latest.articles.length < maxArticlesPerCategory) {
categoriesData.latest.articles.push({
title: postTitle,
url: fullPostUrl,
time: formattedDate,
});
}
// 遍历每个分类,创建或更新分类数据
categories.forEach(category => {
if (!categoriesData[category]) {
categoriesData[category] = {
title: category,
url: `${baseDomain}/categories/${item.categories[0].spec.slug}`,
articles: [],
};
}
// 向分类中添加文章,直到达到最大数量
if (categoriesData[category].articles.length < maxArticlesPerCategory) {
categoriesData[category].articles.push({
title: postTitle,
url: fullPostUrl,
time: formattedDate,
});
}
});
});
} else {
console.error('没有找到 items 数据');
}
// 将分类数据转换为数组并调整顺序,将“最新”分类置于首位
const formattedCategories = Object.values(categoriesData);
const latestCategory = formattedCategories.shift();
formattedCategories.unshift(latestCategory);
// 将分类数据转换为字符串并写入文件
const outputData = `export default ${JSON.stringify(formattedCategories, null, 2)};`;
fs.writeFileSync('./src/data/articles_list.ts', outputData, 'utf8');
console.log('数据已成功写入到 articles_list.ts 文件');
} else {
console.error(`请求失败,状态码: ${response.status}`);
}
} catch (error) {
console.error('请求过程中发生错误:', error.message);
}
}
// 调用函数获取文章数据
fetchPosts().then(r => {}).catch(error => console.error(error));
结合GitHub Actions定期获取数据来进行展示。
name: Fetch Posts
on:
schedule:
- cron: '0 0 * * 1' # 每周一零点执行
workflow_dispatch: # 允许手动触发
jobs:
fetch-and-update:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.PAT_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: |
npm install axios
npm install dotenv
- name: Run fetch script
run: node src/data/getPost.js
env:
BASE_URL: ${{ secrets.BASE_URL }}
API_TOKEN: ${{ secrets.API_TOKEN }}
MAX_ARTICLES_PER_CATEGORY: ${{ secrets.MAX_ARTICLES_PER_CATEGORY }}
- name: Commit and push if changes
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add src/data/articles_list.ts
git diff --quiet && git diff --staged --quiet || (git commit -m "自动更新文章列表" && git push)
这里的执行需要在Actions secrets and variables中设置BASE_URL、API_TOKEN、MAX_ARTICLES_PER_CATEGORY、PAT_TOKEN。
BASE_URL:请求路径,如---/apis/api.console.halo.run/v1alpha1/posts?publishPhase=PUBLISHED
API_TOKEN:个人令牌,在halo的个人页面中获取。
MAX_ARTICLES_PER_CATEGORY:每列最大数据量。
PAT_TOKEN:GitHub机器人提交所需要的Personal access tokens (classic)。
选择卡片
原有的卡片不想展示需要对代码进行删除,过于繁琐,于是我将其修改为可在info.ts进行控制。
// 控制显示的配置
showTitleCard: true,
showBlogCard: true,
showSecondaryCards: false,
showPrimaryCards1: false,
showPrimaryCards2: false,
showMap: true,
showArticles: true,
showProjects: false,
showMusic: false,
showContact: true,
<!--社交网络1-->
{info.showPrimaryCards1 && (
<div class="hidden md:block col-span-4 md:col-span-2 md:col-start-3">
{info.secondaryCards.map((card) => <Card_2x1 info={card} />)}
</div>
)}
<!--社交网络2-->
{info.showPrimaryCards2 && (
<div class="col-span-2 md:col-span-1">
<Card_2x2 info={info.primaryCards[0]} />
<Card_2x2 info={info.primaryCards[1]} />
</div>
<div class="col-span-2 md:col-span-1">
<Card_2x2 info={info.primaryCards[2]} />
<Card_2x2 info={info.primaryCards[3]} />
</div>
)}
<!--地图-->
{info.showMap && (
<div
style="display: none"
class="map col-span-4 md:col-span-2 w-full lg:w-[26rem] lg:h-[26rem] relative md:aspect-[1/1]"
>
<div
class="rounded-xl shadow-xl shadow-accent hover:shadow-gray-500 w-full h-full lg:w-[26rem] lg:h-[26rem] absolute"
id="map-container"
>
</div>
<div
class="absolute bottom-8 right-8 badge badge-neutral p-4 rounded-xl font-semibold"
>
{info.location}
</div>
</div>
)}
<!-- 文章列表 -->
{info.showArticles && (
<div class="col-span-4">
<Card_8x1 info={info.sectionTitles[0]} />
<Articles_List></Articles_List>
</div>
)}
<!-- 个人项目 -->
{info.showProjects && (
<>
<div class="col-span-4">
<Card_8x1 info={info.sectionTitles[1]} />
</div>
<div class="col-span-4 md:col-span-2">
<Card_4x2 info={info.projectCardStart} />
</div>
{info.projectCards.map((card) => (
<div class="col-span-2 md:col-span-1">
<Card_2x2 info={card} />
</div>
))}
<div class="col-span-4 md:col-span-2">
<Card_4x2 info={info.projectCardEnd} />
</div>
</>
)}
<!-- 音乐创作 -->
{info.showMusic && (
<>
<div class="col-span-4">
<Card_8x1 info={info.sectionTitles[2]} />
</div>
{info.musicCards.map((card) => (
<div class="col-span-4 md:col-span-2">
<Card_4x2 info={card} />
</div>
))}
</>
)}
<!-- 联系方式 -->
{info.showContact && (
<>
<div class="col-span-4">
<Card_8x1 info={info.sectionTitles[3]} />
</div>
{info.teleCards.map((card) => (
<div class="col-span-4 md:col-span-2">
<Card_2x1 info={card} />
</div>
))}
</>
)}
部署
使用vercel进行托管,不仅免去服务器而且能与GitHub Actions进行配合,当提交更新数据时vercel也会进行更新,避免手动进行更新。