11import React , { useEffect , useState } from "react" ;
22import "./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+
412interface 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 */
155127function 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 */
167137export 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