Skip to content

Commit edba7c2

Browse files
committed
feat(links): 新增友情链接页面及相关配置和样式
- 在配置文件中添加友情链接示例配置,包括名称、描述、链接及标签 - 在导航菜单中添加友情链接入口,支持移动端隐藏 - 创建友情链接页面组件,展示友情链接列表,支持头像、描述、标签和链接访问 - 增加多语言支持,完善中英文友情链接页面文案 - 添加友情链接页面样式,支持响应式布局和交互效果 - 修改路由配置和页面标题管理,支持友情链接页面正常访问和显示
1 parent bdc6745 commit edba7c2

8 files changed

Lines changed: 280 additions & 0 deletions

File tree

config.example.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,25 @@ projects:
135135
- "Vite"
136136
- "Markdown"
137137

138+
# ========================================
139+
# 友情链接配置
140+
# ========================================
141+
# 展示其他网站、朋友博客、合作伙伴等
142+
links:
143+
- name: "示例博客"
144+
description: "这是一个示例友情链接描述"
145+
url: "https://example.com"
146+
avatar: "/assets/images/link-avatar.jpg" # 可选:链接站点的头像或 logo
147+
tags:
148+
- "博客"
149+
- "技术"
150+
- name: "GitHub"
151+
description: "世界上最大的代码托管平台"
152+
url: "https://github.com"
153+
tags:
154+
- "开发"
155+
- "开源"
156+
138157
# ========================================
139158
# 部署配置
140159
# ========================================

public/config.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ navigation:
6868
- name: "动态"
6969
path: "/news"
7070
showInMobile: false # 放入汉堡菜单
71+
- name: "友情链接"
72+
path: "/links"
73+
showInMobile: false # 放入汉堡菜单
7174

7275
# 主题配置
7376
theme:
@@ -146,6 +149,23 @@ projects:
146149
- "JavaScript"
147150
- "Web"
148151

152+
# 友情链接配置
153+
# 展示其他网站、朋友博客、合作伙伴等
154+
links:
155+
- name: "示例博客"
156+
description: "这是一个示例友情链接描述"
157+
url: "https://example.com"
158+
avatar: "/assets/images/link-avatar.jpg" # 可选:链接站点的头像或 logo
159+
tags:
160+
- "博客"
161+
- "技术"
162+
- name: "GitHub"
163+
description: "世界上最大的代码托管平台"
164+
url: "https://github.com"
165+
tags:
166+
- "开发"
167+
- "开源"
168+
149169
# 部署配置
150170
# 重要:请修改为你自己的仓库地址!
151171
# 对于自定义域名 shiqi-wang.com,建议使用根路径部署

src/App.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Posts } from './pages/Posts'
1313
import { Pages } from './pages/Pages'
1414
import { Files } from './pages/Files'
1515
import { News } from './pages/News'
16+
import { Links } from './pages/Links'
1617
import { NotFound } from './pages/NotFound'
1718
import { DynamicDocumentPage } from './pages/DynamicDocumentPage'
1819
import { generateFolderConfigs } from './utils/folderScanner'
@@ -50,6 +51,8 @@ function DocumentTitleManager({ folderConfigs }) {
5051
pageTitle = t('pages.files')
5152
} else if (pathname === 'news') {
5253
pageTitle = t('pages.news')
54+
} else if (pathname === 'links') {
55+
pageTitle = t('pages.links')
5356
} else {
5457
// 检查是否是动态生成的文档集合页面
5558
const folderConfig = folderConfigs.find(cfg => cfg.name === pathname)
@@ -129,6 +132,7 @@ function AppContent() {
129132
<Route path="pages" element={<Pages />} />
130133
<Route path="files" element={<Files />} />
131134
<Route path="news" element={<News />} />
135+
<Route path="links" element={<Links />} />
132136

133137
{/* 动态生成的路由 - 始终渲染,即使 loading */}
134138
{folderConfigs.map(config => (

src/components/layout/Header.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ function getNavigationLabel(path, t) {
2222
'/pages': 'pages.docs',
2323
'/files': 'pages.files',
2424
'/news': 'pages.news',
25+
'/links': 'pages.links',
2526
'/tutorials': 'pages.tutorials',
2627
'/development': 'pages.development',
2728
}

src/i18n/locales/en.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const en = {
2222
docs: 'Docs',
2323
files: 'Files',
2424
news: 'News',
25+
links: 'Links',
2526
tutorials: 'Tutorials',
2627
development: 'Development',
2728
},
@@ -178,4 +179,12 @@ export const en = {
178179
backHome: 'Back to Home',
179180
goBack: 'Go Back',
180181
},
182+
183+
// Links page
184+
links: {
185+
title: 'Friend Links',
186+
empty: 'No links yet',
187+
emptyHint: 'Add links in the links section of config.yml',
188+
visitSite: 'Visit Site →',
189+
},
181190
}

src/i18n/locales/zh.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const zh = {
2222
docs: '文档',
2323
files: '文件',
2424
news: '动态',
25+
links: '友情链接',
2526
tutorials: '教程中心',
2627
development: '开发指南',
2728
},
@@ -174,4 +175,12 @@ export const zh = {
174175
backHome: '返回首页',
175176
goBack: '返回上一页',
176177
},
178+
179+
// 友情链接页面
180+
links: {
181+
title: '友情链接',
182+
empty: '暂无友情链接',
183+
emptyHint: '在 config.yml 的 links 配置中添加友情链接',
184+
visitSite: '访问网站 →',
185+
},
177186
}

src/pages/Links.jsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import React from 'react'
2+
import { useConfig } from '../config/ConfigContext'
3+
import { useI18n } from '../i18n/I18nContext'
4+
import styles from './Links.module.css'
5+
6+
/**
7+
* 友情链接页面组件
8+
*/
9+
export function Links() {
10+
const { config } = useConfig()
11+
const { t } = useI18n()
12+
const links = config?.links || []
13+
14+
return (
15+
<div className={styles.links}>
16+
<h1 className={styles.title}>{t('links.title')}</h1>
17+
18+
{links.length === 0 ? (
19+
<div className={styles.emptyContainer}>
20+
<p className={styles.empty}>{t('links.empty')}</p>
21+
<p className={styles.emptyHint}>{t('links.emptyHint')}</p>
22+
</div>
23+
) : (
24+
<div className={styles.grid}>
25+
{links.map((link, index) => (
26+
<article key={index} className={styles.card}>
27+
{link.avatar && (
28+
<div className={styles.avatarContainer}>
29+
<img
30+
src={link.avatar}
31+
alt={link.name}
32+
className={styles.avatar}
33+
onError={e => {
34+
// 如果图片加载失败,隐藏图片容器
35+
e.target.style.display = 'none'
36+
}}
37+
/>
38+
</div>
39+
)}
40+
41+
<div className={styles.content}>
42+
<h2 className={styles.name}>{link.name}</h2>
43+
44+
{link.description && (
45+
<p className={styles.description}>{link.description}</p>
46+
)}
47+
48+
{link.tags && link.tags.length > 0 && (
49+
<div className={styles.tags}>
50+
{link.tags.map((tag, tagIndex) => (
51+
<span key={tagIndex} className={styles.tag}>
52+
{tag}
53+
</span>
54+
))}
55+
</div>
56+
)}
57+
58+
{link.url && (
59+
<a
60+
href={link.url}
61+
className={styles.linkButton}
62+
target="_blank"
63+
rel="noopener noreferrer"
64+
>
65+
{t('links.visitSite')}
66+
</a>
67+
)}
68+
</div>
69+
</article>
70+
))}
71+
</div>
72+
)}
73+
</div>
74+
)
75+
}

src/pages/Links.module.css

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
.links {
2+
max-width: 1200px;
3+
margin: 0 auto;
4+
}
5+
6+
.title {
7+
font-size: 2.5rem;
8+
font-weight: 700;
9+
color: var(--text-primary);
10+
margin-bottom: var(--spacing-xl);
11+
padding-bottom: var(--spacing-lg);
12+
border-bottom: 2px solid var(--border-color);
13+
}
14+
15+
.emptyContainer {
16+
text-align: center;
17+
padding: var(--spacing-xl);
18+
}
19+
20+
.empty {
21+
color: var(--text-tertiary);
22+
font-size: var(--font-size-lg);
23+
margin-bottom: var(--spacing-md);
24+
}
25+
26+
.emptyHint {
27+
color: var(--text-tertiary);
28+
font-size: var(--font-size-sm);
29+
}
30+
31+
.grid {
32+
display: grid;
33+
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
34+
gap: var(--spacing-lg);
35+
}
36+
37+
.card {
38+
background-color: var(--bg-secondary);
39+
border: 1px solid var(--border-color);
40+
border-radius: var(--border-radius-lg);
41+
padding: var(--spacing-lg);
42+
transition: all var(--transition-base);
43+
display: flex;
44+
flex-direction: column;
45+
gap: var(--spacing-md);
46+
}
47+
48+
.card:hover {
49+
box-shadow: var(--shadow-md);
50+
transform: translateY(-4px);
51+
border-color: var(--primary-color);
52+
}
53+
54+
.avatarContainer {
55+
display: flex;
56+
justify-content: center;
57+
margin-bottom: var(--spacing-sm);
58+
}
59+
60+
.avatar {
61+
width: 80px;
62+
height: 80px;
63+
border-radius: 50%;
64+
object-fit: cover;
65+
border: 2px solid var(--border-color);
66+
transition: all var(--transition-base);
67+
}
68+
69+
.card:hover .avatar {
70+
border-color: var(--primary-color);
71+
transform: scale(1.05);
72+
}
73+
74+
.content {
75+
display: flex;
76+
flex-direction: column;
77+
flex: 1;
78+
}
79+
80+
.name {
81+
font-size: 1.5rem;
82+
font-weight: 600;
83+
color: var(--text-primary);
84+
margin: 0 0 var(--spacing-sm);
85+
text-align: center;
86+
}
87+
88+
.description {
89+
color: var(--text-secondary);
90+
line-height: 1.6;
91+
margin: 0 0 var(--spacing-md);
92+
flex: 1;
93+
text-align: center;
94+
}
95+
96+
.tags {
97+
display: flex;
98+
gap: var(--spacing-sm);
99+
flex-wrap: wrap;
100+
margin-bottom: var(--spacing-md);
101+
justify-content: center;
102+
}
103+
104+
.tag {
105+
background-color: var(--bg-tertiary);
106+
color: var(--text-secondary);
107+
padding: var(--spacing-xs) var(--spacing-sm);
108+
border-radius: var(--border-radius-sm);
109+
font-size: var(--font-size-sm);
110+
}
111+
112+
.linkButton {
113+
color: var(--link-color);
114+
text-decoration: none;
115+
font-weight: 500;
116+
transition: all var(--transition-base);
117+
align-self: center;
118+
padding: var(--spacing-sm) var(--spacing-lg);
119+
border: 1px solid var(--link-color);
120+
border-radius: var(--border-radius-md);
121+
}
122+
123+
.linkButton:hover {
124+
color: var(--bg-primary);
125+
background-color: var(--link-color);
126+
text-decoration: none;
127+
}
128+
129+
/* 响应式设计 */
130+
@media (max-width: 768px) {
131+
.title {
132+
font-size: 2rem;
133+
}
134+
135+
.grid {
136+
grid-template-columns: 1fr;
137+
}
138+
139+
.avatar {
140+
width: 60px;
141+
height: 60px;
142+
}
143+
}

0 commit comments

Comments
 (0)