Skip to content

Commit 91a5b05

Browse files
committed
feat: 添加GitHub Star徽标到导航栏
- 新增GitHubStarBadge组件,显示仓库star数量 - 支持30分钟缓存机制,减少API调用 - 点击徽标跳转到GitHub仓库 - 集成到导航栏右上角,保持UI设计一致性 - 支持加载状态、错误处理和缓存降级 - 响应式设计,悬停效果和动画过渡
1 parent 3118f3e commit 91a5b05

File tree

2 files changed

+272
-14
lines changed

2 files changed

+272
-14
lines changed

src/App.tsx

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
// src/App.tsx
22
import { HashRouter as Router, Routes, Route, Link, useLocation } from 'react-router-dom';
3-
import { ThemeProvider, createTheme, AppBar, Toolbar, Container, styled } from '@mui/material';
3+
import { ThemeProvider, createTheme, AppBar, Toolbar, Container, Box, styled } from '@mui/material';
44
import Home from './pages/Home';
55
import PromptTemplate from './pages/PromptTemplate';
66
import About from './pages/About';
77
import ProfilesPage from './pages/ProfilesPage';
8+
import GitHubStarBadge from './components/GitHubStarBadge';
89

910
// 创建自定义主题
1011
const theme = createTheme({
@@ -80,19 +81,28 @@ function NavLinks() {
8081
};
8182

8283
return (
83-
<Toolbar sx={{ gap: 1 }}>
84-
<StyledLink to="/" className={isActive('/') ? 'active' : ''}>
85-
首页
86-
</StyledLink>
87-
<StyledLink to="/prompt" className={isActive('/prompt') ? 'active' : ''}>
88-
提示词构建
89-
</StyledLink>
90-
<StyledLink to="/profiles" className={isActive('/profiles') ? 'active' : ''}>
91-
我的Profile
92-
</StyledLink>
93-
<StyledLink to="/about" className={isActive('/about') ? 'active' : ''}>
94-
关于我们
95-
</StyledLink>
84+
<Toolbar sx={{ gap: 1, justifyContent: 'space-between' }}>
85+
{/* 左侧导航链接 */}
86+
<Box sx={{ display: 'flex', gap: 1 }}>
87+
<StyledLink to="/" className={isActive('/') ? 'active' : ''}>
88+
首页
89+
</StyledLink>
90+
<StyledLink to="/prompt" className={isActive('/prompt') ? 'active' : ''}>
91+
提示词构建
92+
</StyledLink>
93+
<StyledLink to="/profiles" className={isActive('/profiles') ? 'active' : ''}>
94+
我的Profile
95+
</StyledLink>
96+
<StyledLink to="/about" className={isActive('/about') ? 'active' : ''}>
97+
关于我们
98+
</StyledLink>
99+
</Box>
100+
101+
{/* 右侧GitHub Star徽标 */}
102+
<GitHubStarBadge
103+
owner="ai-coding-labs"
104+
repo="ai-coding-prompt-builder"
105+
/>
96106
</Toolbar>
97107
);
98108
}

src/components/GitHubStarBadge.tsx

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
/**
2+
* GitHub Star徽标组件
3+
* 显示仓库的star数量,支持缓存和点击跳转
4+
*/
5+
6+
import React, { useState, useEffect } from 'react';
7+
import {
8+
Box,
9+
Button,
10+
Typography,
11+
Tooltip,
12+
CircularProgress
13+
} from '@mui/material';
14+
import {
15+
Star as StarIcon,
16+
GitHub as GitHubIcon
17+
} from '@mui/icons-material';
18+
19+
interface GitHubStarBadgeProps {
20+
owner: string;
21+
repo: string;
22+
className?: string;
23+
}
24+
25+
interface GitHubRepoData {
26+
stargazers_count: number;
27+
html_url: string;
28+
full_name: string;
29+
}
30+
31+
interface CachedData {
32+
data: GitHubRepoData;
33+
timestamp: number;
34+
}
35+
36+
const CACHE_DURATION = 30 * 60 * 1000; // 30分钟缓存
37+
const CACHE_KEY_PREFIX = 'github_star_cache_';
38+
39+
const GitHubStarBadge: React.FC<GitHubStarBadgeProps> = ({
40+
owner,
41+
repo,
42+
className
43+
}) => {
44+
const [starCount, setStarCount] = useState<number | null>(null);
45+
const [repoUrl, setRepoUrl] = useState<string>('');
46+
const [loading, setLoading] = useState(true);
47+
const [error, setError] = useState<string | null>(null);
48+
49+
const cacheKey = `${CACHE_KEY_PREFIX}${owner}_${repo}`;
50+
51+
// 从缓存获取数据
52+
const getCachedData = (): GitHubRepoData | null => {
53+
try {
54+
const cached = localStorage.getItem(cacheKey);
55+
if (cached) {
56+
const parsedCache: CachedData = JSON.parse(cached);
57+
const now = Date.now();
58+
59+
// 检查缓存是否过期
60+
if (now - parsedCache.timestamp < CACHE_DURATION) {
61+
return parsedCache.data;
62+
} else {
63+
// 清除过期缓存
64+
localStorage.removeItem(cacheKey);
65+
}
66+
}
67+
} catch (error) {
68+
console.error('Error reading cache:', error);
69+
localStorage.removeItem(cacheKey);
70+
}
71+
return null;
72+
};
73+
74+
// 保存数据到缓存
75+
const setCachedData = (data: GitHubRepoData) => {
76+
try {
77+
const cacheData: CachedData = {
78+
data,
79+
timestamp: Date.now()
80+
};
81+
localStorage.setItem(cacheKey, JSON.stringify(cacheData));
82+
} catch (error) {
83+
console.error('Error saving cache:', error);
84+
}
85+
};
86+
87+
// 获取GitHub仓库信息
88+
const fetchRepoData = async (): Promise<GitHubRepoData> => {
89+
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`);
90+
91+
if (!response.ok) {
92+
throw new Error(`GitHub API error: ${response.status}`);
93+
}
94+
95+
return response.json();
96+
};
97+
98+
// 格式化star数量
99+
const formatStarCount = (count: number): string => {
100+
if (count >= 1000) {
101+
return `${(count / 1000).toFixed(1)}k`;
102+
}
103+
return count.toString();
104+
};
105+
106+
// 加载数据
107+
useEffect(() => {
108+
const loadData = async () => {
109+
setLoading(true);
110+
setError(null);
111+
112+
try {
113+
// 首先尝试从缓存获取
114+
const cachedData = getCachedData();
115+
if (cachedData) {
116+
setStarCount(cachedData.stargazers_count);
117+
setRepoUrl(cachedData.html_url);
118+
setLoading(false);
119+
return;
120+
}
121+
122+
// 缓存未命中,从API获取
123+
const repoData = await fetchRepoData();
124+
setStarCount(repoData.stargazers_count);
125+
setRepoUrl(repoData.html_url);
126+
127+
// 保存到缓存
128+
setCachedData(repoData);
129+
130+
} catch (err) {
131+
console.error('Error fetching GitHub data:', err);
132+
setError(err instanceof Error ? err.message : 'Failed to fetch data');
133+
134+
// 如果API失败,尝试使用过期的缓存数据
135+
const cachedData = getCachedData();
136+
if (cachedData) {
137+
setStarCount(cachedData.stargazers_count);
138+
setRepoUrl(cachedData.html_url);
139+
setError(null);
140+
}
141+
} finally {
142+
setLoading(false);
143+
}
144+
};
145+
146+
loadData();
147+
}, [owner, repo]);
148+
149+
// 点击处理
150+
const handleClick = () => {
151+
if (repoUrl) {
152+
window.open(repoUrl, '_blank', 'noopener,noreferrer');
153+
}
154+
};
155+
156+
// 加载状态
157+
if (loading) {
158+
return (
159+
<Box
160+
className={className}
161+
sx={{
162+
display: 'flex',
163+
alignItems: 'center',
164+
gap: 1,
165+
px: 2,
166+
py: 1,
167+
borderRadius: 2,
168+
bgcolor: 'background.paper',
169+
border: '1px solid',
170+
borderColor: 'divider'
171+
}}
172+
>
173+
<GitHubIcon sx={{ fontSize: 20 }} />
174+
<CircularProgress size={16} />
175+
</Box>
176+
);
177+
}
178+
179+
// 错误状态
180+
if (error && starCount === null) {
181+
return (
182+
<Tooltip title={`Error: ${error}`}>
183+
<Box
184+
className={className}
185+
sx={{
186+
display: 'flex',
187+
alignItems: 'center',
188+
gap: 1,
189+
px: 2,
190+
py: 1,
191+
borderRadius: 2,
192+
bgcolor: 'error.light',
193+
color: 'error.contrastText',
194+
cursor: 'pointer'
195+
}}
196+
onClick={handleClick}
197+
>
198+
<GitHubIcon sx={{ fontSize: 20 }} />
199+
<Typography variant="caption">Error</Typography>
200+
</Box>
201+
</Tooltip>
202+
);
203+
}
204+
205+
// 正常显示
206+
return (
207+
<Tooltip title={`Star this project on GitHub (${starCount} stars)`}>
208+
<Button
209+
className={className}
210+
onClick={handleClick}
211+
sx={{
212+
display: 'flex',
213+
alignItems: 'center',
214+
gap: 1,
215+
px: 2,
216+
py: 1,
217+
borderRadius: 2,
218+
bgcolor: 'background.paper',
219+
border: '1px solid',
220+
borderColor: 'divider',
221+
color: 'text.primary',
222+
textTransform: 'none',
223+
minWidth: 'auto',
224+
'&:hover': {
225+
bgcolor: 'action.hover',
226+
borderColor: 'primary.main',
227+
transform: 'translateY(-1px)',
228+
boxShadow: 2
229+
},
230+
transition: 'all 0.2s ease-in-out'
231+
}}
232+
>
233+
<GitHubIcon sx={{ fontSize: 20 }} />
234+
<StarIcon sx={{ fontSize: 16, color: '#ffd700' }} />
235+
<Typography variant="body2" sx={{ fontWeight: 500 }}>
236+
{starCount !== null ? formatStarCount(starCount) : '0'}
237+
</Typography>
238+
{error && (
239+
<Typography variant="caption" sx={{ color: 'warning.main', ml: 0.5 }}>
240+
(cached)
241+
</Typography>
242+
)}
243+
</Button>
244+
</Tooltip>
245+
);
246+
};
247+
248+
export default GitHubStarBadge;

0 commit comments

Comments
 (0)