1.全部改成参数式部件
2.可以用脚本创建文章
This commit is contained in:
parent
ad02d7b38b
commit
f110beb5dc
55
create_post.sh
Normal file
55
create_post.sh
Normal file
@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 获取脚本所在目录的上级目录(假设脚本在项目根目录)
|
||||
PROJECT_ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
# 如果没有提供参数,使用交互式输入
|
||||
if [ "$#" -lt 2 ]; then
|
||||
read -rp "请输入文章标题: " TITLE
|
||||
read -rp "请输入文章路径 (例如: web/my-post): " PATH_ARG
|
||||
else
|
||||
TITLE=$1
|
||||
PATH_ARG=$2
|
||||
fi
|
||||
|
||||
# 检查输入是否为空
|
||||
if [ -z "$TITLE" ] || [ -z "$PATH_ARG" ]; then
|
||||
echo "错误: 标题和路径不能为空"
|
||||
echo "使用方法: $0 <标题> <路径>"
|
||||
echo "示例: $0 \"我的新文章\" \"web/my-post\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 获取当前时间,格式化为ISO 8601格式
|
||||
CURRENT_DATE=$(date +"%Y-%m-%dT%H:%M:%S%:z")
|
||||
|
||||
# 构建完整的文件路径
|
||||
CONTENT_DIR="$PROJECT_ROOT/src/content"
|
||||
FULL_PATH="$CONTENT_DIR/$PATH_ARG"
|
||||
|
||||
# 确保路径存在
|
||||
mkdir -p "$FULL_PATH"
|
||||
|
||||
# 构建最终的文件路径
|
||||
FILENAME="$FULL_PATH/$(basename "$PATH_ARG").md"
|
||||
ABSOLUTE_PATH="$(cd "$(dirname "$FILENAME")" 2>/dev/null && pwd)/$(basename "$FILENAME")"
|
||||
|
||||
# 检查文件是否已存在
|
||||
if [ -f "$FILENAME" ]; then
|
||||
echo "错误: 文章已存在: $ABSOLUTE_PATH"
|
||||
read -rp "按回车键退出..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 创建markdown文件
|
||||
cat > "$FILENAME" << EOF
|
||||
---
|
||||
title: "$TITLE"
|
||||
date: $CURRENT_DATE
|
||||
tags: []
|
||||
---
|
||||
hello,world
|
||||
EOF
|
||||
|
||||
echo "已创建新文章: $ABSOLUTE_PATH"
|
||||
read -rp "按回车键退出..."
|
@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import ReactMasonryCss from 'react-masonry-css';
|
||||
import { GIT_CONFIG } from '@/consts';
|
||||
|
||||
// Git 平台类型枚举
|
||||
export enum GitPlatform {
|
||||
@ -9,30 +8,37 @@ export enum GitPlatform {
|
||||
GITEE = 'gitee'
|
||||
}
|
||||
|
||||
// 内部使用的平台配置 - 用户不需要修改
|
||||
export const GIT_PLATFORM_CONFIG = {
|
||||
platforms: {
|
||||
[GitPlatform.GITHUB]: {
|
||||
...GIT_CONFIG.github,
|
||||
apiUrl: 'https://api.github.com'
|
||||
},
|
||||
[GitPlatform.GITEA]: {
|
||||
...GIT_CONFIG.gitea
|
||||
},
|
||||
[GitPlatform.GITEE]: {
|
||||
...GIT_CONFIG.gitee,
|
||||
apiUrl: 'https://gitee.com/api/v5'
|
||||
}
|
||||
},
|
||||
enabledPlatforms: [GitPlatform.GITHUB, GitPlatform.GITEA, GitPlatform.GITEE],
|
||||
platformNames: {
|
||||
[GitPlatform.GITHUB]: 'GitHub',
|
||||
[GitPlatform.GITEA]: 'Gitea',
|
||||
[GitPlatform.GITEE]: 'Gitee'
|
||||
}
|
||||
// Git 配置接口
|
||||
export type GitConfig = {
|
||||
username: string;
|
||||
token?: string;
|
||||
perPage?: number;
|
||||
url?: string;
|
||||
};
|
||||
|
||||
// 平台默认配置
|
||||
export const DEFAULT_GIT_CONFIG = {
|
||||
perPage: 10,
|
||||
giteaUrl: ''
|
||||
};
|
||||
|
||||
// 内部使用的平台配置
|
||||
export const GIT_PLATFORM_CONFIG = {
|
||||
platforms: {
|
||||
[GitPlatform.GITHUB]: {
|
||||
apiUrl: 'https://api.github.com'
|
||||
},
|
||||
[GitPlatform.GITEA]: {},
|
||||
[GitPlatform.GITEE]: {
|
||||
apiUrl: 'https://gitee.com/api/v5'
|
||||
}
|
||||
},
|
||||
platformNames: {
|
||||
[GitPlatform.GITHUB]: 'GitHub',
|
||||
[GitPlatform.GITEA]: 'Gitea',
|
||||
[GitPlatform.GITEE]: 'Gitee'
|
||||
}
|
||||
};
|
||||
|
||||
interface GitProject {
|
||||
name: string;
|
||||
@ -59,13 +65,15 @@ interface GitProjectCollectionProps {
|
||||
username?: string;
|
||||
organization?: string;
|
||||
title?: string;
|
||||
config: GitConfig;
|
||||
}
|
||||
|
||||
const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
|
||||
platform,
|
||||
username,
|
||||
organization,
|
||||
title
|
||||
title,
|
||||
config
|
||||
}) => {
|
||||
const [projects, setProjects] = useState<GitProject[]>([]);
|
||||
const [pagination, setPagination] = useState<Pagination>({ current: 1, total: 1, hasNext: false, hasPrev: false });
|
||||
@ -73,36 +81,49 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isPageChanging, setIsPageChanging] = useState(false);
|
||||
|
||||
// 获取默认用户名
|
||||
const defaultUsername = GIT_PLATFORM_CONFIG.platforms[platform].username;
|
||||
// 使用提供的用户名或默认用户名
|
||||
const effectiveUsername = username || defaultUsername;
|
||||
const effectiveUsername = username || config.username;
|
||||
|
||||
const fetchData = async (page = 1) => {
|
||||
setLoading(true);
|
||||
const params = new URLSearchParams();
|
||||
params.append('platform', platform);
|
||||
params.append('page', page.toString());
|
||||
|
||||
if (effectiveUsername) {
|
||||
params.append('username', effectiveUsername);
|
||||
if (!platform || !Object.values(GitPlatform).includes(platform)) {
|
||||
setError('无效的平台参数');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (organization) {
|
||||
params.append('organization', organization);
|
||||
}
|
||||
|
||||
const url = `/api/git-projects?${params.toString()}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`获取数据失败: ${response.status} ${response.statusText}`);
|
||||
const baseUrl = new URL('/api/git-projects', window.location.origin);
|
||||
|
||||
baseUrl.searchParams.append('platform', platform);
|
||||
baseUrl.searchParams.append('page', page.toString());
|
||||
baseUrl.searchParams.append('config', JSON.stringify(config));
|
||||
|
||||
if (effectiveUsername) {
|
||||
baseUrl.searchParams.append('username', effectiveUsername);
|
||||
}
|
||||
|
||||
if (organization) {
|
||||
baseUrl.searchParams.append('organization', organization);
|
||||
}
|
||||
|
||||
const response = await fetch(baseUrl.toString(), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(`请求失败: ${response.status} ${response.statusText}\n${JSON.stringify(errorData, null, 2)}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setProjects(data.projects);
|
||||
setPagination(data.pagination);
|
||||
} catch (err) {
|
||||
console.error('请求错误:', err);
|
||||
setError(err instanceof Error ? err.message : '未知错误');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
@ -2,7 +2,7 @@
|
||||
import "@/styles/global.css";
|
||||
import Header from "@/components/header.astro";
|
||||
import Footer from "@/components/Footer.astro";
|
||||
import { ICP, PSB_ICP, PSB_ICP_URL, SITE_NAME } from "@/consts";
|
||||
import { ICP, PSB_ICP, PSB_ICP_URL, SITE_NAME, SITE_DESCRIPTION } from "@/consts";
|
||||
|
||||
// 定义Props接口
|
||||
interface Props {
|
||||
@ -18,7 +18,7 @@ interface Props {
|
||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||
|
||||
// 从props中获取页面特定信息
|
||||
const { title = SITE_NAME, description, date, author, tags, image } = Astro.props;
|
||||
const { title = SITE_NAME, description = SITE_DESCRIPTION, date, author, tags, image } = Astro.props;
|
||||
---
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" class="m-0 w-full h-full">
|
||||
|
@ -2,9 +2,10 @@
|
||||
interface Props {
|
||||
type: 'movie' | 'book';
|
||||
title: string;
|
||||
doubanId: string;
|
||||
}
|
||||
|
||||
const { type, title } = Astro.props;
|
||||
const { type, title, doubanId } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
@ -24,7 +25,7 @@ const { type, title } = Astro.props;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script is:inline define:vars={{ type }}>
|
||||
<script is:inline define:vars={{ type, doubanId }}>
|
||||
|
||||
let currentPage = 1;
|
||||
let isLoading = false;
|
||||
@ -41,7 +42,7 @@ const { type, title } = Astro.props;
|
||||
|
||||
const start = (page - 1) * itemsPerPage;
|
||||
try {
|
||||
const response = await fetch(`/api/douban?type=${type}&start=${start}`);
|
||||
const response = await fetch(`/api/douban?type=${type}&start=${start}&doubanId=${doubanId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`获取${type === 'movie' ? '电影' : '图书'}数据失败`);
|
||||
}
|
||||
|
@ -2,9 +2,12 @@ import React, { useEffect, useRef } from 'react';
|
||||
import * as echarts from 'echarts';
|
||||
import worldData from '@/assets/world.zh.json';
|
||||
import chinaData from '@/assets/china.json';
|
||||
import { VISITED_PLACES } from '@/consts';
|
||||
|
||||
const WorldHeatmap: React.FC = () => {
|
||||
interface WorldHeatmapProps {
|
||||
visitedPlaces: string[];
|
||||
}
|
||||
|
||||
const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
|
||||
const chartRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@ -12,7 +15,6 @@ const WorldHeatmap: React.FC = () => {
|
||||
|
||||
const chart = echarts.init(chartRef.current);
|
||||
|
||||
// 合并中国省份到世界地图
|
||||
const mergedWorldData = {
|
||||
...worldData,
|
||||
features: worldData.features.map((feature: any) => {
|
||||
@ -21,13 +23,12 @@ const WorldHeatmap: React.FC = () => {
|
||||
...feature,
|
||||
geometry: {
|
||||
type: 'MultiPolygon',
|
||||
coordinates: [] // 清空中国的轮廓
|
||||
coordinates: []
|
||||
}
|
||||
};
|
||||
}
|
||||
return feature;
|
||||
}).concat(
|
||||
// 添加中国省份数据
|
||||
chinaData.features.map((feature: any) => ({
|
||||
...feature,
|
||||
properties: {
|
||||
@ -49,7 +50,7 @@ const WorldHeatmap: React.FC = () => {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: ({name}: {name: string}) => {
|
||||
const visited = VISITED_PLACES.includes(name);
|
||||
const visited = visitedPlaces.includes(name);
|
||||
return `${name}<br/>${visited ? '✓ 已去过' : '尚未去过'}`;
|
||||
}
|
||||
},
|
||||
@ -85,7 +86,7 @@ const WorldHeatmap: React.FC = () => {
|
||||
},
|
||||
data: mergedWorldData.features.map((feature: any) => ({
|
||||
name: feature.properties.name,
|
||||
value: VISITED_PLACES.includes(feature.properties.name) ? 1 : 0
|
||||
value: visitedPlaces.includes(feature.properties.name) ? 1 : 0
|
||||
})),
|
||||
nameProperty: 'name'
|
||||
}]
|
||||
@ -103,7 +104,7 @@ const WorldHeatmap: React.FC = () => {
|
||||
chart.resize();
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
}, [visitedPlaces]);
|
||||
|
||||
return <div ref={chartRef} style={{ width: '100%', height: '600px' }} />;
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
export const SITE_URL = 'https://lsy22.com';
|
||||
export const SITE_NAME = "echoes";
|
||||
export const SITE_DESCRIPTION = "记录生活,分享所思";
|
||||
|
||||
export const NAV_LINKS = [
|
||||
{ href: '/', text: '首页' },
|
||||
@ -14,28 +15,7 @@ export const ICP = '渝ICP备2022009272号';
|
||||
export const PSB_ICP = '渝公网安备50011902000520号';
|
||||
export const PSB_ICP_URL = 'http://www.beian.gov.cn/portal/registerSystemInfo';
|
||||
|
||||
export const VISITED_PLACES = [ '黑龙江', '吉林', '辽宁', '北京', '天津', '广东', '西藏', '河北', '山东', '湖南','重庆','四川' ];
|
||||
export const VISITED_PLACES = ['黑龙江', '吉林', '辽宁', '北京', '天津', '广东', '西藏', '河北', '山东', '湖南', '重庆', '四川'];
|
||||
|
||||
export const DOUBAN_ID = 'lsy22';
|
||||
|
||||
// Git 配置 - 只包含用户需要修改的内容
|
||||
export const GIT_CONFIG = {
|
||||
// 每页显示的项目数量
|
||||
perPage: 10,
|
||||
|
||||
// 用户配置 - 用户只需修改这部分
|
||||
github: {
|
||||
username: 'lsy2246', // GitHub 用户名
|
||||
token: '' // GitHub 访问令牌(可选)
|
||||
},
|
||||
gitea: {
|
||||
url: 'https://g.lsy22.com', // Gitea 实例 URL
|
||||
username: 'lsy', // Gitea 用户名
|
||||
token: '' // Gitea 访问令牌(可选)
|
||||
},
|
||||
gitee: {
|
||||
username: 'lsy22', // Gitee 用户名
|
||||
token: '' // Gitee 访问令牌(可选)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -63,32 +63,6 @@ export function getSpecialPath(originalPath: string): string {
|
||||
return originalPath;
|
||||
}
|
||||
|
||||
// 辅助函数:标准化文件名
|
||||
function normalizeFileName(fileName: string): string {
|
||||
// 先转换为小写
|
||||
let normalized = fileName.toLowerCase();
|
||||
|
||||
// 保存括号中的内容
|
||||
const bracketContent = normalized.match(/[((](.*?)[))]/)?.[1] || '';
|
||||
|
||||
// 标准化处理
|
||||
normalized = normalized
|
||||
.replace(/[((].*?[))]/g, '') // 移除括号及其内容
|
||||
.replace(/[【】\[\]]/g, '') // 移除方括号
|
||||
.replace(/[—–]/g, '-') // 统一全角横线为半角
|
||||
.replace(/\s+/g, '-') // 空格转换为连字符
|
||||
.replace(/[.:;,'"!?`]/g, '') // 移除标点符号
|
||||
.replace(/-+/g, '-') // 合并多个连字符
|
||||
.replace(/^-|-$/g, ''); // 移除首尾连字符
|
||||
|
||||
// 如果括号中有内容,将其添加回去
|
||||
if (bracketContent) {
|
||||
normalized = `${normalized}-${bracketContent}`;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
// 3. 定义目录结构处理函数
|
||||
async function getContentStructure(): Promise<ContentStructure> {
|
||||
// 获取所有文章
|
||||
@ -101,7 +75,6 @@ async function getContentStructure(): Promise<ContentStructure> {
|
||||
// 处理每个文章路径
|
||||
for (const articlePath of articlePaths) {
|
||||
const parts = articlePath.split('/');
|
||||
const fileName = parts[parts.length - 1];
|
||||
const dirPath = parts.slice(0, -1);
|
||||
|
||||
// 为每一级目录创建或更新节点
|
||||
|
255
src/content/web/echoes博客使用说明.md
Normal file
255
src/content/web/echoes博客使用说明.md
Normal file
@ -0,0 +1,255 @@
|
||||
---
|
||||
title: "echoes博客使用说明"
|
||||
date: 2025-03-09T01:07:23Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
这是一个基于 Astro + React 构建的个人博客系统,具有文章管理、项目展示、观影记录、读书记录等功能。本文将详细介绍如何使用和配置这个博客系统。
|
||||
|
||||
## 功能特点
|
||||
|
||||
1. **响应式设计**:完美适配桌面端和移动端
|
||||
2. **深色模式**:支持自动和手动切换深色/浅色主题
|
||||
3. **文章系统**:支持 Markdown 写作,带有标签和分类
|
||||
4. **项目展示**:支持展示 GitHub、Gitea 和 Gitee 的项目
|
||||
5. **观影记录**:集成豆瓣观影数据
|
||||
6. **读书记录**:集成豆瓣读书数据
|
||||
|
||||
## 基础配置
|
||||
|
||||
主要配置文件位于 `src/consts.ts`,你需要修改以下内容:
|
||||
|
||||
```typescript
|
||||
// 网站基本信息
|
||||
export const SITE_URL = 'https://your-domain.com';
|
||||
export const SITE_NAME = "你的网站名称";
|
||||
export const SITE_DESCRIPTION = "网站描述";
|
||||
|
||||
// 导航链接
|
||||
export const NAV_LINKS = [
|
||||
{ href: '/', text: '首页' },
|
||||
{ href: '/articles', text: '文章' },
|
||||
{ href: '/movies', text: '观影' },
|
||||
{ href: '/books', text: '读书' },
|
||||
{ href: '/projects', text: '项目' },
|
||||
{ href: '/other', text: '其他' }
|
||||
];
|
||||
|
||||
// 备案信息(如果需要)
|
||||
export const ICP = '你的ICP备案号';
|
||||
export const PSB_ICP = '你的公安备案号';
|
||||
export const PSB_ICP_URL = '备案链接';
|
||||
|
||||
// 豆瓣配置
|
||||
export const DOUBAN_ID = '你的豆瓣ID';
|
||||
```
|
||||
|
||||
## 文章写作
|
||||
|
||||
### 创建新文章
|
||||
|
||||
你可以通过以下两种方式创建新文章:
|
||||
|
||||
#### 1. 使用创建脚本(推荐)
|
||||
|
||||
项目根目录下提供了 `create_post.sh` 脚本来快速创建文章:
|
||||
|
||||
```bash
|
||||
# 添加执行权限(首次使用时)
|
||||
chmod +x create_post.sh
|
||||
|
||||
# 方式1:交互式创建
|
||||
./create_post.sh
|
||||
# 按提示输入文章标题和路径
|
||||
|
||||
# 方式2:命令行参数创建
|
||||
./create_post.sh "文章标题" "目录/文章路径"
|
||||
# 例如:./create_post.sh "我的新文章" "web/my-post"
|
||||
```
|
||||
|
||||
脚本会自动:
|
||||
|
||||
- 在指定位置创建文章文件
|
||||
- 添加必要的 frontmatter(标题、日期、标签)
|
||||
- 检查文件是否已存在
|
||||
- 显示文件的绝对路径
|
||||
|
||||
#### 2. 手动创建
|
||||
|
||||
在 `src/content/articles` 目录下创建 `.md` 或 `.mdx` 文件。文章需要包含以下前置信息:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: "文章标题"
|
||||
date: YYYY-MM-DD
|
||||
tags: ["标签1", "标签2"]
|
||||
---
|
||||
|
||||
文章内容...
|
||||
```
|
||||
|
||||
### 文章列表展示
|
||||
|
||||
文章列表页面会自动获取所有文章并按日期排序展示,支持:
|
||||
|
||||
- 文章标题和摘要
|
||||
- 发布日期
|
||||
- 标签系统
|
||||
- 阅读时间估算
|
||||
|
||||
## 项目展示
|
||||
|
||||
项目展示页面支持从 GitHub、Gitea 和 Gitee 获取和展示项目信息。
|
||||
|
||||
### GitProjectCollection 组件
|
||||
|
||||
用于展示 Git 平台的项目列表。
|
||||
|
||||
基本用法:
|
||||
|
||||
```astro
|
||||
---
|
||||
import GitProjectCollection from '@/components/GitProjectCollection';
|
||||
import { GitPlatform, type GitConfig } from '@/components/GitProjectCollection';
|
||||
|
||||
// Gitea 配置示例
|
||||
const giteaConfig: GitConfig = {
|
||||
username: 'your-username', // 必填:用户名
|
||||
token: 'your-token', // 可选:访问令牌,用于访问私有仓库
|
||||
perPage: 10, // 可选:每页显示数量,默认 10
|
||||
url: 'your-git-url' // Gitea 必填,GitHub/Gitee 无需填写
|
||||
};
|
||||
---
|
||||
|
||||
<GitProjectCollection
|
||||
platform={GitPlatform.GITEA} // 平台类型:GITHUB、GITEA、GITEE
|
||||
username="your-username" // 可选:覆盖 config 中的用户名
|
||||
title="Git 项目" // 显示标题
|
||||
config={giteaConfig} // 平台配置
|
||||
client:load // Astro 指令:客户端加载
|
||||
/>
|
||||
```
|
||||
|
||||
## 观影和读书记录
|
||||
|
||||
### MediaGrid 组件
|
||||
|
||||
`MediaGrid` 组件用于展示豆瓣的观影和读书记录。
|
||||
|
||||
#### 基本用法
|
||||
|
||||
```astro
|
||||
---
|
||||
import MediaGrid from '@/components/MediaGrid.astro';
|
||||
---
|
||||
|
||||
// 展示电影记录
|
||||
<MediaGrid
|
||||
type="movie" // 类型:movie 或 book
|
||||
title="我看过的电影" // 显示标题
|
||||
doubanId={DOUBAN_ID} // 使用配置文件中的豆瓣ID
|
||||
/>
|
||||
|
||||
// 展示读书记录
|
||||
<MediaGrid
|
||||
type="book"
|
||||
title="我读过的书"
|
||||
doubanId={DOUBAN_ID}
|
||||
/>
|
||||
```
|
||||
|
||||
## 主题切换
|
||||
|
||||
系统支持三种主题模式:
|
||||
|
||||
1. 跟随系统
|
||||
2. 手动切换浅色模式
|
||||
3. 手动切换深色模式
|
||||
|
||||
主题设置会被保存在浏览器的 localStorage 中。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 环境要求
|
||||
|
||||
- Node.js 18+
|
||||
- npm 或 pnpm
|
||||
|
||||
### 安装步骤
|
||||
|
||||
1. 克隆项目
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-username/echoes.git
|
||||
cd echoes
|
||||
```
|
||||
|
||||
2. 安装依赖
|
||||
|
||||
```bash
|
||||
npm install
|
||||
# 或者使用 pnpm
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. 修改配置
|
||||
|
||||
编辑 `src/consts.ts` 文件,更新网站配置信息。
|
||||
|
||||
4. 本地运行
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# 或者使用 pnpm
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
访问 `http://localhost:4321` 查看效果。
|
||||
|
||||
## 部署说明
|
||||
|
||||
### 本地构建部署
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
构建产物位于 `dist` 目录,将其部署到你的服务器即可。
|
||||
|
||||
### Vercel 部署
|
||||
|
||||
本项目完全支持 Vercel 部署,你可以通过以下步骤快速部署:
|
||||
|
||||
1. Fork 本项目到你的 GitHub 账号
|
||||
|
||||
2. 在 Vercel 控制台中点击 "New Project"
|
||||
|
||||
3. 导入你 fork 的 GitHub 仓库
|
||||
|
||||
4. 配置构建选项:
|
||||
- Framework Preset: Astro
|
||||
- Build Command: `astro build`
|
||||
- Output Directory: `dist`
|
||||
- Install Command: `npm install` 或 `pnpm install`
|
||||
|
||||
5. 点击 "Deploy" 开始部署
|
||||
|
||||
Vercel 会自动检测项目类型并应用正确的构建配置。每次你推送代码到 main 分支时,Vercel 都会自动重新部署。
|
||||
|
||||
#### 环境变量配置
|
||||
|
||||
如果你使用了需要环境变量的功能(如 API tokens),需要在 Vercel 项目设置中的 "Environment Variables" 部分添加相应的环境变量。
|
||||
|
||||
## 常见问题
|
||||
|
||||
1. **图片无法显示**
|
||||
- 检查图片路径是否正确
|
||||
- 确保图片已放入 `public` 目录
|
||||
|
||||
2. **豆瓣数据无法获取**
|
||||
- 确认豆瓣 ID 配置正确
|
||||
- 检查豆瓣记录是否公开
|
||||
|
||||
3. **Git 项目无法显示**
|
||||
- 验证用户名配置
|
||||
- 确认 API 访问限制
|
@ -1,226 +0,0 @@
|
||||
---
|
||||
title: "echoes博客使用说明"
|
||||
date: 2025-03-09T01:07:23Z
|
||||
tags: []
|
||||
---
|
||||
|
||||
这是一个基于 Astro + React 构建的个人博客系统,具有文章管理、项目展示、观影记录、读书记录等功能。本文将详细介绍如何使用和配置这个博客系统。
|
||||
|
||||
## 功能特点
|
||||
|
||||
1. **响应式设计**:完美适配桌面端和移动端
|
||||
2. **深色模式**:支持自动和手动切换深色/浅色主题
|
||||
3. **文章系统**:支持 Markdown 写作,带有标签和分类
|
||||
4. **项目展示**:支持展示 GitHub、Gitea 和 Gitee 的项目
|
||||
5. **观影记录**:集成豆瓣观影数据
|
||||
6. **读书记录**:集成豆瓣读书数据
|
||||
7. **旅行地图**:世界地图展示旅行足迹
|
||||
|
||||
## 基础配置
|
||||
|
||||
主要配置文件位于 `src/consts.ts`,你需要修改以下内容:
|
||||
|
||||
```typescript
|
||||
// 网站基本信息
|
||||
export const SITE_URL = 'https://your-domain.com';
|
||||
export const SITE_NAME = "你的网站名称";
|
||||
|
||||
// 导航链接
|
||||
export const NAV_LINKS = [
|
||||
{ href: '/', text: '首页' },
|
||||
{ href: '/articles', text: '文章' },
|
||||
// ... 可以根据需要修改
|
||||
];
|
||||
|
||||
// 备案信息(如果需要)
|
||||
export const ICP = '你的ICP备案号';
|
||||
export const PSB_ICP = '你的公安备案号';
|
||||
export const PSB_ICP_URL = '备案链接';
|
||||
|
||||
// 旅行足迹
|
||||
export const VISITED_PLACES = ['你去过的地方'];
|
||||
|
||||
// 豆瓣ID
|
||||
export const DOUBAN_ID = '你的豆瓣ID';
|
||||
|
||||
// Git平台配置
|
||||
export const GIT_CONFIG = {
|
||||
github: {
|
||||
username: '你的GitHub用户名',
|
||||
token: '你的GitHub令牌' // 可选
|
||||
},
|
||||
gitea: {
|
||||
url: '你的Gitea实例地址',
|
||||
username: '你的Gitea用户名',
|
||||
token: '你的Gitea令牌' // 可选
|
||||
},
|
||||
gitee: {
|
||||
username: '你的Gitee用户名',
|
||||
token: '你的Gitee令牌' // 可选
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 文章写作
|
||||
|
||||
### 创建新文章
|
||||
|
||||
在 `src/content/articles` 目录下创建 `.md` 或 `.mdx` 文件。文章需要包含以下前置信息:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: "文章标题"
|
||||
date: YYYY-MM-DD
|
||||
tags: ["标签1", "标签2"]
|
||||
---
|
||||
|
||||
文章内容...
|
||||
```
|
||||
|
||||
### 文章列表展示
|
||||
|
||||
文章列表页面会自动获取所有文章并按日期排序展示,支持:
|
||||
|
||||
- 文章标题和摘要
|
||||
- 发布日期
|
||||
- 标签系统
|
||||
- 阅读时间估算
|
||||
|
||||
## 项目展示
|
||||
|
||||
项目展示页面会自动从配置的 Git 平台获取你的项目信息,展示:
|
||||
|
||||
- 项目名称和描述
|
||||
- Star 和 Fork 数
|
||||
- 主要编程语言
|
||||
- 最后更新时间
|
||||
|
||||
要启用项目展示,需要:
|
||||
|
||||
1. 在 `consts.ts` 中配置相应平台的用户名
|
||||
2. 如果需要访问私有仓库,配置相应的访问令牌
|
||||
|
||||
## 观影和读书记录
|
||||
|
||||
系统会自动从豆瓣获取你的观影和读书记录,展示:
|
||||
|
||||
- 电影/书籍封面
|
||||
- 标题
|
||||
- 评分
|
||||
- 观看/阅读日期
|
||||
|
||||
要启用此功能,需要:
|
||||
|
||||
1. 在 `consts.ts` 中配置你的豆瓣 ID
|
||||
2. 确保你的豆瓣观影/读书记录是公开的
|
||||
|
||||
## 旅行地图
|
||||
|
||||
世界地图会根据 `VISITED_PLACES` 配置自动标记你去过的地方。支持:
|
||||
|
||||
- 中国省份级别的标记
|
||||
- 世界国家级别的标记
|
||||
- 交互式缩放和平移
|
||||
- 鼠标悬停显示地名
|
||||
|
||||
## 主题切换
|
||||
|
||||
系统支持三种主题模式:
|
||||
|
||||
1. 跟随系统
|
||||
2. 手动切换浅色模式
|
||||
3. 手动切换深色模式
|
||||
|
||||
主题设置会被保存在浏览器的 localStorage 中。
|
||||
|
||||
## 性能优化
|
||||
|
||||
本博客系统采用了多项性能优化措施:
|
||||
|
||||
1. 静态页面生成
|
||||
2. 图片懒加载
|
||||
3. 代码分割
|
||||
4. 样式按需加载
|
||||
5. 响应式图片
|
||||
|
||||
## 部署说明
|
||||
|
||||
### 传统部署
|
||||
|
||||
1. 构建项目:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
2. 构建产物位于 `dist` 目录
|
||||
|
||||
3. 将 `dist` 目录部署到你的服务器或静态托管平台
|
||||
|
||||
### Vercel 部署
|
||||
|
||||
Vercel 提供了最简单的部署方式,只需要几个步骤:
|
||||
|
||||
1. 在 GitHub 上创建你的项目仓库并推送代码
|
||||
|
||||
2. 访问 [Vercel](https://vercel.com) 并使用 GitHub 账号登录
|
||||
|
||||
3. 点击 "New Project",然后选择你的博客仓库
|
||||
|
||||
4. 配置部署选项:
|
||||
- Framework Preset: 选择 "Astro"
|
||||
- Build Command: 保持默认的 `npm run build`
|
||||
- Output Directory: 保持默认的 `dist`
|
||||
- Install Command: 保持默认的 `npm install`
|
||||
|
||||
5. 点击 "Deploy" 按钮
|
||||
|
||||
部署完成后,Vercel 会自动为你的项目分配一个域名。你也可以在项目设置中添加自定义域名。
|
||||
|
||||
**优势:**
|
||||
|
||||
- 自动构建和部署
|
||||
- 自动 HTTPS
|
||||
- 全球 CDN
|
||||
- 自动预览部署(每个 PR 都会生成预览链接)
|
||||
- 零配置持续部署
|
||||
|
||||
## 常见问题
|
||||
|
||||
1. **图片无法显示**
|
||||
- 检查图片路径是否正确
|
||||
- 确保图片已放入 `public` 目录
|
||||
|
||||
2. **豆瓣数据无法获取**
|
||||
- 确认豆瓣 ID 配置正确
|
||||
- 检查豆瓣记录是否公开
|
||||
|
||||
3. **Git 项目无法显示**
|
||||
- 验证用户名配置
|
||||
- 检查访问令牌是否有效
|
||||
- 确认 API 访问限制
|
||||
|
||||
## 更新日志
|
||||
|
||||
### 2024-03-21
|
||||
|
||||
- 初始版本发布
|
||||
- 支持基本的博客功能
|
||||
- 集成豆瓣数据展示
|
||||
- 添加旅行地图功能
|
||||
|
||||
## 后续计划
|
||||
|
||||
1. 添加评论系统
|
||||
2. 优化移动端体验
|
||||
3. 增加更多自定义主题选项
|
||||
4. 添加文章搜索功能
|
||||
5. 支持更多外部服务集成
|
||||
|
||||
## 贡献指南
|
||||
|
||||
欢迎提交 Issue 和 Pull Request 来改进这个博客系统。在提交之前,请确保:
|
||||
|
||||
1. 代码符合项目的代码风格
|
||||
2. 新功能有适当的测试覆盖
|
||||
3. 文档已经更新
|
@ -18,12 +18,6 @@ export const GET: APIRoute = async ({ request }) => {
|
||||
// 获取所有文章
|
||||
const articles = await getCollection('articles');
|
||||
|
||||
// 打印所有文章的ID,用于调试
|
||||
console.log('所有文章的ID:');
|
||||
articles.forEach(article => {
|
||||
console.log(`- ${article.id}`);
|
||||
});
|
||||
|
||||
// 根据条件过滤文章
|
||||
let filteredArticles = articles;
|
||||
|
||||
@ -37,17 +31,10 @@ export const GET: APIRoute = async ({ request }) => {
|
||||
// 如果有路径过滤,直接使用文章ID来判断
|
||||
if (path) {
|
||||
const normalizedPath = path.toLowerCase();
|
||||
console.log('当前过滤路径:', normalizedPath);
|
||||
|
||||
filteredArticles = filteredArticles.filter(article => {
|
||||
const articlePath = article.id.split('/');
|
||||
console.log('处理文章:', article.id, '分割后:', articlePath);
|
||||
|
||||
// 检查文章路径的每一部分
|
||||
return article.id.toLowerCase().includes(normalizedPath);
|
||||
});
|
||||
|
||||
console.log('过滤后的文章数量:', filteredArticles.length);
|
||||
}
|
||||
|
||||
// 按日期排序(最新的在前面)
|
||||
|
@ -1,6 +1,5 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { load } from 'cheerio';
|
||||
import { DOUBAN_ID } from '@/consts';
|
||||
|
||||
// 添加服务器渲染标记
|
||||
export const prerender = false;
|
||||
@ -9,13 +8,21 @@ export const GET: APIRoute = async ({ request }) => {
|
||||
const url = new URL(request.url);
|
||||
const type = url.searchParams.get('type') || 'movie';
|
||||
const start = parseInt(url.searchParams.get('start') || '0');
|
||||
const doubanId = url.searchParams.get('doubanId'); // 从查询参数获取 doubanId
|
||||
|
||||
if (!doubanId) {
|
||||
return new Response(JSON.stringify({ error: '缺少豆瓣ID' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
let doubanUrl = '';
|
||||
if (type === 'book') {
|
||||
doubanUrl = `https://book.douban.com/people/${DOUBAN_ID}/collect?start=${start}&sort=time&rating=all&filter=all&mode=grid`;
|
||||
doubanUrl = `https://book.douban.com/people/${doubanId}/collect?start=${start}&sort=time&rating=all&filter=all&mode=grid`;
|
||||
} else {
|
||||
doubanUrl = `https://movie.douban.com/people/${DOUBAN_ID}/collect?start=${start}&sort=time&rating=all&filter=all&mode=grid`;
|
||||
doubanUrl = `https://movie.douban.com/people/${doubanId}/collect?start=${start}&sort=time&rating=all&filter=all&mode=grid`;
|
||||
}
|
||||
|
||||
const response = await fetch(doubanUrl, {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import type { APIContext } from 'astro';
|
||||
import { Octokit } from 'octokit';
|
||||
import fetch from 'node-fetch';
|
||||
import { GIT_CONFIG } from '@/consts';
|
||||
import { GitPlatform, GIT_PLATFORM_CONFIG } from '@/components/GitProjectCollection';
|
||||
import { GitPlatform } from '@/components/GitProjectCollection';
|
||||
|
||||
interface GitProject {
|
||||
name: string;
|
||||
@ -24,75 +23,108 @@ interface Pagination {
|
||||
hasPrev: boolean;
|
||||
}
|
||||
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
const url = new URL(request.url);
|
||||
const platformParam = url.searchParams.get('platform') || 'github';
|
||||
const platform = platformParam as GitPlatform;
|
||||
const page = parseInt(url.searchParams.get('page') || '1');
|
||||
const username = url.searchParams.get('username') || '';
|
||||
const organization = url.searchParams.get('organization') || '';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
export async function GET({ request }: APIContext) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
|
||||
const headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
const platformParam = url.searchParams.get('platform');
|
||||
const page = parseInt(url.searchParams.get('page') || '1');
|
||||
const username = url.searchParams.get('username') || '';
|
||||
const organization = url.searchParams.get('organization') || '';
|
||||
const configStr = url.searchParams.get('config');
|
||||
|
||||
if (!platformParam) {
|
||||
return new Response(JSON.stringify({
|
||||
error: '无效的平台参数',
|
||||
receivedPlatform: platformParam,
|
||||
}), { status: 400, headers });
|
||||
}
|
||||
|
||||
if (!configStr) {
|
||||
return new Response(JSON.stringify({
|
||||
error: '缺少配置参数'
|
||||
}), { status: 400, headers });
|
||||
}
|
||||
|
||||
const config = JSON.parse(configStr);
|
||||
|
||||
if (!Object.values(GitPlatform).includes(platformParam as GitPlatform)) {
|
||||
return new Response(JSON.stringify({
|
||||
error: '无效的平台参数',
|
||||
receivedPlatform: platformParam,
|
||||
}), { status: 400, headers });
|
||||
}
|
||||
|
||||
const platform = platformParam as GitPlatform;
|
||||
let projects: GitProject[] = [];
|
||||
let pagination: Pagination = { current: page, total: 1, hasNext: false, hasPrev: page > 1 };
|
||||
|
||||
if (platform === GitPlatform.GITHUB) {
|
||||
const result = await fetchGithubProjects(username, organization, page);
|
||||
const result = await fetchGithubProjects(username, organization, page, config);
|
||||
projects = result.projects;
|
||||
pagination = result.pagination;
|
||||
} else if (platform === GitPlatform.GITEA) {
|
||||
const result = await fetchGiteaProjects(username, organization, page);
|
||||
const result = await fetchGiteaProjects(username, organization, page, config);
|
||||
projects = result.projects;
|
||||
pagination = result.pagination;
|
||||
} else if (platform === GitPlatform.GITEE) {
|
||||
try {
|
||||
const result = await fetchGiteeProjects(username, organization, page);
|
||||
projects = result.projects;
|
||||
pagination = result.pagination;
|
||||
} catch (giteeError) {
|
||||
// 返回空数据而不是抛出错误
|
||||
}
|
||||
const result = await fetchGiteeProjects(username, organization, page, config);
|
||||
projects = result.projects;
|
||||
pagination = result.pagination;
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ projects, pagination }), {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
headers
|
||||
});
|
||||
} catch (error) {
|
||||
let errorMessage = '获取数据失败';
|
||||
if (error instanceof Error) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
error: errorMessage,
|
||||
platform
|
||||
error: '处理请求错误',
|
||||
message: error instanceof Error ? error.message : '未知错误'
|
||||
}), {
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchGithubProjects(username: string, organization: string, page: number) {
|
||||
// 添加重试逻辑
|
||||
export function OPTIONS() {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchGithubProjects(username: string, organization: string, page: number, config: any) {
|
||||
const maxRetries = 3;
|
||||
let retryCount = 0;
|
||||
|
||||
while (retryCount < maxRetries) {
|
||||
try {
|
||||
const octokit = new Octokit({
|
||||
auth: GIT_PLATFORM_CONFIG.platforms[GitPlatform.GITHUB].token || process.env.GITHUB_TOKEN,
|
||||
auth: process.env.GITHUB_TOKEN,
|
||||
request: {
|
||||
timeout: 10000 // 增加超时时间到10秒
|
||||
timeout: 10000
|
||||
}
|
||||
});
|
||||
|
||||
const perPage = GIT_CONFIG.perPage;
|
||||
const perPage = config.perPage || 10;
|
||||
let repos;
|
||||
|
||||
if (organization) {
|
||||
@ -114,10 +146,8 @@ async function fetchGithubProjects(username: string, organization: string, page:
|
||||
});
|
||||
repos = data;
|
||||
} else {
|
||||
// 如果没有指定用户或组织,使用默认用户名
|
||||
const defaultUsername = GIT_PLATFORM_CONFIG.platforms[GitPlatform.GITHUB].username;
|
||||
const { data } = await octokit.request('GET /users/{username}/repos', {
|
||||
username: defaultUsername,
|
||||
username: config.username,
|
||||
per_page: perPage,
|
||||
page: page,
|
||||
sort: 'updated',
|
||||
@ -126,20 +156,16 @@ async function fetchGithubProjects(username: string, organization: string, page:
|
||||
repos = data;
|
||||
}
|
||||
|
||||
// 替换获取分页信息的代码
|
||||
let hasNext = false;
|
||||
let hasPrev = page > 1;
|
||||
let totalPages = 1;
|
||||
|
||||
// 使用响应头中的 Link 信息
|
||||
if (repos.length === perPage) {
|
||||
hasNext = true;
|
||||
totalPages = page + 1;
|
||||
}
|
||||
|
||||
// 或者使用 GitHub API 的 repository_count 估算
|
||||
if (repos.length > 0 && repos[0].owner) {
|
||||
// 简单估算:如果有结果且等于每页数量,则可能有下一页
|
||||
hasNext = repos.length === perPage;
|
||||
totalPages = hasNext ? page + 1 : page;
|
||||
}
|
||||
@ -173,12 +199,10 @@ async function fetchGithubProjects(username: string, organization: string, page:
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 等待一段时间后重试
|
||||
await new Promise(resolve => setTimeout(resolve, 2000 * retryCount));
|
||||
}
|
||||
}
|
||||
|
||||
// 添加默认返回值,防止 undefined
|
||||
return {
|
||||
projects: [],
|
||||
pagination: {
|
||||
@ -190,17 +214,10 @@ async function fetchGithubProjects(username: string, organization: string, page:
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchGiteaProjects(username: string, organization: string, page: number) {
|
||||
async function fetchGiteaProjects(username: string, organization: string, page: number, config: any) {
|
||||
try {
|
||||
// 使用consts中的配置
|
||||
const perPage = GIT_CONFIG.perPage;
|
||||
const platformConfig = GIT_PLATFORM_CONFIG.platforms[GitPlatform.GITEA];
|
||||
|
||||
if (!platformConfig) {
|
||||
throw new Error('Gitea 平台配置不存在');
|
||||
}
|
||||
|
||||
const giteaUrl = platformConfig.url;
|
||||
const perPage = config.perPage || 10;
|
||||
const giteaUrl = config.url;
|
||||
|
||||
if (!giteaUrl) {
|
||||
throw new Error('Gitea URL 不存在');
|
||||
@ -212,16 +229,13 @@ async function fetchGiteaProjects(username: string, organization: string, page:
|
||||
} else if (username) {
|
||||
apiUrl = `${giteaUrl}/api/v1/users/${username}/repos?page=${page}&per_page=${perPage}`;
|
||||
} else {
|
||||
const defaultUsername = GIT_PLATFORM_CONFIG.platforms[GitPlatform.GITEA].username;
|
||||
apiUrl = `${giteaUrl}/api/v1/users/${defaultUsername}/repos?page=${page}&per_page=${perPage}`;
|
||||
apiUrl = `${giteaUrl}/api/v1/users/${config.username}/repos?page=${page}&per_page=${perPage}`;
|
||||
}
|
||||
|
||||
const response = await fetch(apiUrl, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
...(GIT_PLATFORM_CONFIG.platforms[GitPlatform.GITEA].token ?
|
||||
{ 'Authorization': `token ${GIT_PLATFORM_CONFIG.platforms[GitPlatform.GITEA].token}` } :
|
||||
{})
|
||||
...(config.token ? { 'Authorization': `token ${config.token}` } : {})
|
||||
}
|
||||
});
|
||||
|
||||
@ -231,10 +245,8 @@ async function fetchGiteaProjects(username: string, organization: string, page:
|
||||
|
||||
const data = await response.json() as any;
|
||||
|
||||
// Gitea API 返回的是数组
|
||||
const repos = Array.isArray(data) ? data : [];
|
||||
|
||||
// 获取分页信息
|
||||
const totalCount = parseInt(response.headers.get('X-Total-Count') || '0');
|
||||
const totalPages = Math.ceil(totalCount / perPage) || 1;
|
||||
|
||||
@ -261,7 +273,6 @@ async function fetchGiteaProjects(username: string, organization: string, page:
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// 返回空数据而不是抛出错误
|
||||
return {
|
||||
projects: [],
|
||||
pagination: {
|
||||
@ -274,19 +285,16 @@ async function fetchGiteaProjects(username: string, organization: string, page:
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchGiteeProjects(username: string, organization: string, page: number) {
|
||||
async function fetchGiteeProjects(username: string, organization: string, page: number, config: any) {
|
||||
try {
|
||||
// 使用consts中的配置
|
||||
const perPage = GIT_CONFIG.perPage;
|
||||
const perPage = config.perPage || 10;
|
||||
|
||||
// 确定用户名
|
||||
const giteeUsername = username || GIT_CONFIG.gitee.username;
|
||||
const giteeUsername = username || config.username;
|
||||
|
||||
if (!giteeUsername) {
|
||||
throw new Error('Gitee 用户名未配置');
|
||||
}
|
||||
|
||||
// 构建API URL
|
||||
let apiUrl;
|
||||
if (organization) {
|
||||
apiUrl = `https://gitee.com/api/v5/orgs/${organization}/repos?page=${page}&per_page=${perPage}&sort=updated&direction=desc`;
|
||||
@ -294,9 +302,8 @@ async function fetchGiteeProjects(username: string, organization: string, page:
|
||||
apiUrl = `https://gitee.com/api/v5/users/${giteeUsername}/repos?page=${page}&per_page=${perPage}&sort=updated&direction=desc`;
|
||||
}
|
||||
|
||||
// 添加访问令牌(如果有)
|
||||
if (GIT_CONFIG.gitee.token) {
|
||||
apiUrl += `&access_token=${GIT_CONFIG.gitee.token}`;
|
||||
if (config.token) {
|
||||
apiUrl += `&access_token=${config.token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(apiUrl);
|
||||
@ -307,7 +314,6 @@ async function fetchGiteeProjects(username: string, organization: string, page:
|
||||
|
||||
const data = await response.json() as any[];
|
||||
|
||||
// 转换数据格式
|
||||
const projects: GitProject[] = data.map(repo => ({
|
||||
name: repo.name || '',
|
||||
description: repo.description || '',
|
||||
@ -321,7 +327,6 @@ async function fetchGiteeProjects(username: string, organization: string, page:
|
||||
platform: GitPlatform.GITEE
|
||||
}));
|
||||
|
||||
// 获取分页信息
|
||||
const totalCount = parseInt(response.headers.get('total_count') || '0');
|
||||
const totalPages = Math.ceil(totalCount / perPage) || 1;
|
||||
|
||||
@ -335,7 +340,6 @@ async function fetchGiteeProjects(username: string, organization: string, page:
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// 返回空结果
|
||||
return {
|
||||
projects: [],
|
||||
pagination: {
|
||||
|
@ -1,9 +1,13 @@
|
||||
---
|
||||
import Layout from '@/components/Layout.astro';
|
||||
import MediaGrid from '@/components/MediaGrid.astro';
|
||||
import { SITE_NAME } from '@/consts';
|
||||
import { SITE_NAME, DOUBAN_ID } from '@/consts';
|
||||
---
|
||||
|
||||
<Layout title={`图书 - ${SITE_NAME}`}>
|
||||
<MediaGrid type="book" title="我读过的书" />
|
||||
<MediaGrid
|
||||
type="book"
|
||||
title="我读过的书"
|
||||
doubanId={DOUBAN_ID}
|
||||
/>
|
||||
</Layout>
|
@ -1,9 +1,13 @@
|
||||
---
|
||||
import Layout from '@/components/Layout.astro';
|
||||
import MediaGrid from '@/components/MediaGrid.astro';
|
||||
import { SITE_NAME } from '@/consts.ts';
|
||||
import { SITE_NAME, DOUBAN_ID } from '@/consts.ts';
|
||||
---
|
||||
|
||||
<Layout title={`电影 - ${SITE_NAME}`}>
|
||||
<MediaGrid type="movie" title="我看过的电影" />
|
||||
<MediaGrid
|
||||
type="movie"
|
||||
title="我看过的电影"
|
||||
doubanId={DOUBAN_ID}
|
||||
/>
|
||||
</Layout>
|
@ -1,11 +1,8 @@
|
||||
---
|
||||
import Layout from "@/components/Layout.astro";
|
||||
import { Countdown } from "@/components/Countdown";
|
||||
// 恢复静态导入
|
||||
import WorldHeatmap from '@/components/WorldHeatmap';
|
||||
|
||||
// 移除动态导入
|
||||
// const WorldHeatmap = await import('@/components/WorldHeatmap').then(mod => mod.default);
|
||||
import { VISITED_PLACES } from '@/consts';
|
||||
---
|
||||
|
||||
<Layout title="退休倒计时">
|
||||
@ -20,7 +17,10 @@ import WorldHeatmap from '@/components/WorldHeatmap';
|
||||
<section class="mb-16">
|
||||
<h2 class="text-3xl font-semibold text-center mb-6">我的旅行足迹</h2>
|
||||
<div class="mx-auto bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 transition-all hover:shadow-xl">
|
||||
<WorldHeatmap client:only="react" />
|
||||
<WorldHeatmap
|
||||
client:only="react"
|
||||
visitedPlaces={VISITED_PLACES}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
@ -1,22 +1,24 @@
|
||||
---
|
||||
import Layout from '@/components/Layout.astro';
|
||||
import { GitPlatform } from '@/components/GitProjectCollection';
|
||||
import GitProjectCollection from '@/components/GitProjectCollection';
|
||||
import { GitPlatform, type GitConfig } from '@/components/GitProjectCollection';
|
||||
|
||||
const giteaConfig: GitConfig = {
|
||||
username: 'lsy',
|
||||
url: 'https://g.lsy22.com',
|
||||
};
|
||||
---
|
||||
|
||||
<Layout title="项目 | echoes">
|
||||
<main class="container mx-auto px-4 py-8">
|
||||
|
||||
<div class="space-y-12">
|
||||
<!-- Gitea 项目集合 -->
|
||||
<GitProjectCollection
|
||||
platform={GitPlatform.GITEA}
|
||||
platform={GitPlatform.GITEA}
|
||||
username="lsy"
|
||||
title="Gitea 个人项目"
|
||||
config={giteaConfig}
|
||||
client:load
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
||||
|
Loading…
Reference in New Issue
Block a user