Skip to content

Commit 822f795

Browse files
committed
⚡️ 优化贡献者获取逻辑
1 parent 2643869 commit 822f795

File tree

2 files changed

+58
-123
lines changed

2 files changed

+58
-123
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"scripts": {
66
"docusaurus": "docusaurus",
77
"start": "docusaurus start",
8-
"prebuild": "node scripts/fetchContributors.js",
98
"build": "docusaurus build",
109
"swizzle": "docusaurus swizzle",
1110
"deploy": "docusaurus deploy",

src/components/ContributorCard/index.tsx

Lines changed: 58 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import React, { useEffect, useState } from "react";
22
import "./styles.css";
33

4+
const CACHE_KEY = "contributors_cache";
5+
const CACHE_DURATION = 2 * 60 * 60 * 1000; // 2小时
6+
7+
interface CacheData {
8+
data: Contributor[];
9+
timestamp: number;
10+
}
11+
412
interface Contributor {
513
id: number;
614
login: string;
@@ -27,6 +35,34 @@ interface ContributorCardItemProps {
2735
rank?: number;
2836
}
2937

38+
/**
39+
* 获取缓存数据
40+
*/
41+
function getCachedContributors(): Contributor[] | null {
42+
try {
43+
const cached = localStorage.getItem(CACHE_KEY);
44+
if (!cached) return null;
45+
46+
const { data, timestamp } = JSON.parse(cached) as CacheData;
47+
const isExpired = Date.now() - timestamp > CACHE_DURATION;
48+
49+
return isExpired ? null : data;
50+
} catch {
51+
return null;
52+
}
53+
}
54+
55+
/**
56+
* 保存缓存数据
57+
*/
58+
function setCachedContributors(data: Contributor[]): void {
59+
try {
60+
localStorage.setItem(CACHE_KEY, JSON.stringify({ data, timestamp: Date.now() }));
61+
} catch {
62+
console.warn("无法保存缓存数据");
63+
}
64+
}
65+
3066
/**
3167
* 获取GitHub贡献者数据(带分页)
3268
* @param {string} repo 仓库名称,格式为 "用户名/仓库名"
@@ -36,20 +72,19 @@ async function fetchContributors(repo: string): Promise<Contributor[]> {
3672
try {
3773
let allContributors: Contributor[] = [];
3874
let page = 1;
39-
let hasNextPage = true;
75+
let hasMore = true;
4076

41-
// 使用循环处理分页,GitHub API默认每页返回30条数据
42-
while (hasNextPage) {
77+
while (hasMore) {
4378
const response = await fetch(`https://api.github.com/repos/${repo}/contributors?per_page=100&page=${page}`);
4479
if (!response.ok) {
4580
throw new Error("获取贡献者数据失败");
4681
}
4782

4883
const data = await response.json();
49-
if (data.length === 0) {
50-
hasNextPage = false;
84+
if (!Array.isArray(data) || data.length === 0) {
85+
hasMore = false;
5186
} else {
52-
allContributors = [...allContributors, ...data];
87+
allContributors.push(...data);
5388
page++;
5489
}
5590
}
@@ -62,69 +97,6 @@ async function fetchContributors(repo: string): Promise<Contributor[]> {
6297
}
6398
}
6499

65-
/**
66-
* 获取所有贡献者的详细统计信息
67-
* @param {string} repo 仓库名称,格式为 "用户名/仓库名"
68-
* @returns {Promise<Array>} 包含统计数据的贡献者数组
69-
*/
70-
async function fetchAllContributorStats(repo: string): Promise<ContributorStats[]> {
71-
try {
72-
// 直接获取全部贡献者统计数据
73-
const response = await fetch(`https://api.github.com/repos/${repo}/stats/contributors`);
74-
if (!response.ok) {
75-
// GitHub可能会返回202,表示正在计算统计信息
76-
if (response.status === 202) {
77-
// 等待几秒后重试
78-
await new Promise((resolve) => setTimeout(resolve, 3000));
79-
return fetchAllContributorStats(repo);
80-
}
81-
throw new Error("获取贡献者统计数据失败");
82-
}
83-
84-
const data = await response.json();
85-
86-
// 确保返回的是数组
87-
if (!Array.isArray(data)) {
88-
console.error("GitHub API返回的统计数据不是数组格式:", data);
89-
return [];
90-
}
91-
92-
return data;
93-
} catch (error) {
94-
console.error("获取贡献者统计数据出错:", error);
95-
return [];
96-
}
97-
}
98-
99-
/**
100-
* 获取单个贡献者的统计信息
101-
* @param {Array} allStats 所有贡献者的统计数据
102-
* @param {string} username 贡献者用户名
103-
* @returns {Object} 贡献者统计数据
104-
*/
105-
function getContributorStats(allStats: ContributorStats[], username: string): { additions: number; deletions: number } {
106-
// 确保 allStats 是数组
107-
if (!Array.isArray(allStats)) {
108-
console.error("获取的统计数据格式错误:", allStats);
109-
return { additions: 0, deletions: 0 };
110-
}
111-
112-
const userStats = allStats.find((stat) => stat && stat.author && stat.author.login === username);
113-
if (!userStats) {
114-
return { additions: 0, deletions: 0 };
115-
}
116-
117-
// 计算总添加和删除行数
118-
let additions = 0;
119-
let deletions = 0;
120-
userStats.weeks.forEach((week) => {
121-
additions += week.a;
122-
deletions += week.d;
123-
});
124-
125-
return { additions, deletions };
126-
}
127-
128100
/**
129101
* 判断用户是否为机器人账户
130102
* @param {string} username 用户名
@@ -153,26 +125,16 @@ function isBot(username: string): boolean {
153125
* @returns {string} 格式化后的字符串
154126
*/
155127
function formatNumber(num: number): string {
156-
if (num >= 1000000) {
157-
return (num / 1000000).toFixed(1) + "M";
158-
} else if (num >= 1000) {
159-
return (num / 1000).toFixed(1) + "k";
160-
}
161-
return num.toString();
128+
return Intl.NumberFormat("en-US", {
129+
notation: "compact",
130+
maximumFractionDigits: 1
131+
}).format(num);
162132
}
163133

164134
/**
165135
* 单个贡献者卡片组件
166136
*/
167137
export function ContributorCardItem({ contributor, rank }: ContributorCardItemProps): React.ReactElement {
168-
// 优先使用详细统计中的增删行数,如果没有则使用贡献数
169-
const additions = contributor.additions || 0;
170-
const deletions = contributor.deletions || 0;
171-
const totalContribution = additions + deletions;
172-
173-
// 判断是否有增删行数数据
174-
const hasLineStats = additions > 0 || deletions > 0;
175-
176138
return (
177139
<div className="contributor-card">
178140
{rank && <div className="contributor-rank">{rank}</div>}
@@ -185,17 +147,7 @@ export function ContributorCardItem({ contributor, rank }: ContributorCardItemPr
185147
{contributor.login}
186148
</a>
187149
</div>
188-
<div className="contributor-stats">
189-
{hasLineStats ? (
190-
<>
191-
<span className="additions">+{formatNumber(additions)}</span>
192-
<span className="deletions">-{formatNumber(deletions)}</span>
193-
</>
194-
) : (
195-
<span className="no-stats">行数统计暂未显示</span>
196-
)}
197-
</div>
198-
<div className="contributor-total">总贡献: {formatNumber(contributor.contributions)} 次提交</div>
150+
<div className="contributor-total">贡献: {formatNumber(contributor.contributions)}</div>
199151
</div>
200152
</div>
201153
);
@@ -219,41 +171,25 @@ export default function ContributorCard({ repo = "Cubic-Project/NitWikit" }: Con
219171
try {
220172
setLoading(true);
221173

174+
const cachedData = getCachedContributors();
175+
if (cachedData) {
176+
setContributors(cachedData);
177+
setLoading(false);
178+
return;
179+
}
180+
222181
// 直接从GitHub API获取贡献者数据
223182
const contributorsData = await fetchContributors(repo);
224183

225184
// 过滤掉机器人账户
226185
const filteredContributors = contributorsData.filter((contributor) => !isBot(contributor.login));
227186

228-
// 尝试获取详细统计数据
229-
let statsData: ContributorStats[] = [];
230-
try {
231-
statsData = await fetchAllContributorStats(repo);
232-
} catch (statsError) {
233-
console.warn("获取详细统计数据失败,将使用基本贡献数据:", statsError);
234-
}
235-
236-
// 合并统计数据到贡献者数据
237-
const contributorsWithStats = filteredContributors.map((contributor) => {
238-
const stats = getContributorStats(statsData, contributor.login);
239-
240-
return {
241-
...contributor,
242-
additions: stats.additions || 0,
243-
deletions: stats.deletions || 0,
244-
total: contributor.contributions || 0
245-
};
246-
});
247-
248-
// 确保所有贡献者有非零贡献值进行排序
249-
const validContributors = contributorsWithStats.filter((c) => c.contributions > 0 || c.total > 0);
250-
251-
// 按照贡献总量排序
252-
const sorted = validContributors.sort(
253-
(a, b) => (b.contributions || b.total) - (a.contributions || a.total)
254-
);
187+
// 排序
188+
const sorted = filteredContributors
189+
.filter((c) => c.contributions > 0)
190+
.sort((a, b) => b.contributions - a.contributions);
255191

256-
console.log(`处理后共有 ${sorted.length} 位有效贡献者`);
192+
setCachedContributors(sorted);
257193
setContributors(sorted);
258194
} catch (err) {
259195
const errorMessage = err instanceof Error ? err.message : "未知错误";

0 commit comments

Comments
 (0)